From 67d58448016e514fad404b5e7baf1f46652d8486 Mon Sep 17 00:00:00 2001 From: Denvi Date: Mon, 11 Jul 2016 19:31:35 +0500 Subject: [PATCH 01/26] VisPy canvas added. --- PlotCanvas.py | 7 +- VisPyCanvas.py | 94 +++++++++++++++++++++++ VisPyVisuals.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 298 insertions(+), 1 deletion(-) create mode 100644 VisPyCanvas.py create mode 100644 VisPyVisuals.py diff --git a/PlotCanvas.py b/PlotCanvas.py index 94469d2..cdf423e 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -17,6 +17,7 @@ from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas from matplotlib.backends.backend_agg import FigureCanvasAgg import FlatCAMApp import logging +from VisPyCanvas import VisPyCanvas log = logging.getLogger('base') @@ -150,7 +151,11 @@ class PlotCanvas(QtCore.QObject): # Attach to parent #self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns?? - self.container.addWidget(self.canvas) # Qt + # self.container.addWidget(self.canvas) # Qt + self.vispy_canvas = VisPyCanvas() + self.vispy_canvas.create_native() + self.vispy_canvas.native.setParent(self.app.ui) + self.container.addWidget(self.vispy_canvas.native) # Copy a bitmap of the canvas for quick animation. # Update every time the canvas is re-drawn. diff --git a/VisPyCanvas.py b/VisPyCanvas.py new file mode 100644 index 0000000..6e66295 --- /dev/null +++ b/VisPyCanvas.py @@ -0,0 +1,94 @@ +import numpy as np +from PyQt4.QtGui import QPalette +import vispy.scene as scene +from VisPyVisuals import LinesCollection, PolygonCollection + + +class VisPyCanvas(scene.SceneCanvas): + + def __init__(self): + scene.SceneCanvas.__init__(self, keys=None) + self.unfreeze() + + back_color = str(QPalette().color(QPalette.Window).name()) + + self.central_widget.bgcolor = back_color + self.central_widget.border_color = back_color + + grid = self.central_widget.add_grid(margin=10) + grid.spacing = 0 + + top_padding = grid.add_widget(row=0, col=0, col_span=2) + top_padding.height_max = 24 + + yaxis = scene.AxisWidget(orientation='left', axis_color='black', text_color='black', font_size=12) + yaxis.width_max = 60 + grid.add_widget(yaxis, row=1, col=0) + + xaxis = scene.AxisWidget(orientation='bottom', axis_color='black', text_color='black', font_size=12) + xaxis.height_max = 40 + grid.add_widget(xaxis, row=2, col=1) + + right_padding = grid.add_widget(row=0, col=2, row_span=2) + right_padding.width_max = 24 + + view = grid.add_view(row=1, col=1, border_color='black', bgcolor='white') + view.camera = scene.PanZoomCamera(aspect=1) + + # -------------------------- Tests ---------------------------------------- + + data1 = np.empty((4, 2)) + data2 = np.empty((4, 2)) + + data1[0] = 0, -0.0 + data1[1] = 1, -0. + data1[2] = 1, 1 + data1[3] = 0, 1 + + data2[0] = 2, -0.0 + data2[1] = 3, -0. + data2[2] = 3, 1 + data2[3] = 2, 1 + + # lines = LinesCollection(width=2) + # + # print "add1", lines.add(data1, color='blue') + # print "add2", lines.add(data2, color='green') + # + # lines.remove(0) + # # lines.remove(1) + # + # # view.add(lines) + # + # polys = PolygonCollection(border_width=2) + # polys.add(data1, color='yellow', border_color='red') + # polys.add(data2, color='green', border_color='blue') + # + # view.add(polys) + + # polys.remove(0) + # polys.remove(1) + + # ----------------------- End of tests ---------------------------- + + test = {'Poly': []} + + test['Poly'].append(3) + test['Poly'].append(5) + # test['Poly'] = np.append(test['Poly'], 1) + # test['Poly'] = np.append(test['Poly'], 4) + + print "test", test + + self.primitives = {} + self.primitives['Line'] = LinesCollection(parent=view.scene) + self.primitives['Poly'] = PolygonCollection(parent=view.scene) + + grid1 = scene.GridLines(parent=view.scene, color='gray') + grid1.set_gl_state(depth_test=False) + + xaxis.link_view(view) + yaxis.link_view(view) + + self.view = view + self.freeze() diff --git a/VisPyVisuals.py b/VisPyVisuals.py new file mode 100644 index 0000000..640d603 --- /dev/null +++ b/VisPyVisuals.py @@ -0,0 +1,198 @@ +from vispy.visuals import LineVisual, PolygonVisual +from vispy.scene.visuals import create_visual_node +from vispy.geometry import PolygonData +from vispy.gloo import set_state +from matplotlib.colors import ColorConverter +import numpy as np +import abc + + +class Collection: + + def __init__(self): + self.data = {} + self.last_key = -1 + self.colors = {} + self.border_colors = {} + + def add(self, data, form=None, color=None, border_color=None, update=False): + self.last_key += 1 + self.data[self.last_key] = self._translate_data(data, form) + self.colors[self.last_key] = color + self.border_colors[self.last_key] = border_color + if update: + self._update_data() + + return self.last_key + + def remove(self, key, update=False): + del self.data[key] + del self.colors[key] + del self.border_colors[key] + if update: + self._update_data() + + def redraw(self): + self._update_data() + + @abc.abstractmethod + def _update_data(self): + pass + + def _translate_data(self, data, form): + return data + + +class LinesCollectionVisual(LineVisual, Collection): + + def __init__(self, width=1, method='gl', antialias=False): + self.color_converter = ColorConverter() + Collection.__init__(self) + LineVisual.__init__(self, width=width, connect='segments', method=method, antialias=antialias) + + def _update_data(self): + if len(self.data) > 0: + # Merge arrays of segments, colors of segments + pos_m = np.empty((0, 2)) # Line segments + color_m = np.empty((0, 4)) # Colors of segments + + for key in self.data: + if self.colors[key] is None: + continue + pos = self.data[key] + color = np.full((len(pos), 4), self.color_converter.to_rgba(self.colors[key])) + pos_m = np.append(pos_m, pos, 0) + color_m = np.append(color_m, color, 0) + + # Update lines + if len(pos_m) > 0: + LineVisual.set_data(self, pos_m, color=color_m) + + else: + self._bounds = None + self._pos = None + self._changed['pos'] = True + self.update() + + def _translate_data(self, data, form): + if form == 'LineString': + return linestring_to_segments(data) + elif form == 'LinearRing': + return linearring_to_segments(data) + else: + return data + + def set_data(self, pos=None, color=None, width=None, connect=None): + pass + + +class PolygonCollectionVisual(PolygonVisual, Collection): + + def __init__(self, color='black', border_width=1, **kwargs): + self.color_converter = ColorConverter() + Collection.__init__(self) + PolygonVisual.__init__(self, border_width=border_width, **kwargs) + + def _update_data(self): + if len(self.data) > 0: + # Merge arrays of vertices, faces, face colors + pts_m = np.empty((0, 2)) # Vertices of triangles + tris_m = np.empty((0, 3)) # Indexes of vertices of faces + colors_m = np.empty((0, 4)) # Colors of faces + + for key in self.data: + if self.colors[key] is None: + continue + pos = self.data[key] + data = PolygonData(vertices=np.array(pos, dtype=np.float32)) + + pts, tris = data.triangulate() + offset = np.full((1, 3), len(pts_m)) + tris = np.add(tris, offset) + + pts_m, tris_m = np.append(pts_m, pts, 0), np.append(tris_m, tris, 0) + + colors = np.full((len(tris), 4), self.color_converter.to_rgba(self.colors[key])) + colors_m = np.append(colors_m, colors, 0) + + # Update mesh + if len(pts_m) > 0: + set_state(polygon_offset_fill=False) + self._mesh.set_data(vertices=pts_m, faces=tris_m.astype(np.uint32), + face_colors=colors_m) + + # Merge arrays of segments, colors of segments + pos_m = np.empty((0, 2)) # Line segments of borders + border_colors_m = np.empty((0, 4)) # Colors of segments + + for key in self.data: + if self.border_colors[key] is None: + continue + pos = self.data[key] + pos = linearring_to_segments(pos) + pos_m = np.append(pos_m, pos, 0) + + border_colors = np.full((len(pos), 4), self.color_converter.to_rgba(self.border_colors[key])) + border_colors_m = np.append(border_colors_m, border_colors, 0) + + # Update borders + if len(pos_m) > 0: + self._border.set_data(pos=pos_m, color=border_colors_m, width=self._border_width, + connect='segments') + self._border.update() + + else: + self._mesh.set_data() + self._border._bounds = None + self._border._pos = None + self._border._changed['pos'] = True + self._border.update() + + def _update(self): + self._update_data() + + @property + def pos(self): + return None + + @pos.setter + def pos(self, pos): + pass + + @property + def color(self): + return None + + @color.setter + def color(self, color): + pass + + @property + def border_color(self): + return None + + @border_color.setter + def border_color(self, border_color): + pass + + +def linearring_to_segments(arr): + + # Close linear ring + if np.any(arr[0] != arr[1]): + arr = np.concatenate([arr, arr[:1]], axis=0) + + return linestring_to_segments(arr) + + +def linestring_to_segments(arr): + lines = [] + for pnt in range(0, len(arr) - 1): + lines.append(arr[pnt]) + lines.append(arr[pnt + 1]) + + return np.array(lines) + + +LinesCollection = create_visual_node(LinesCollectionVisual) +PolygonCollection = create_visual_node(PolygonCollectionVisual) From 20605499c37c52b2c43e37c21a0a0d976530f195 Mon Sep 17 00:00:00 2001 From: Denvi Date: Mon, 11 Jul 2016 23:29:05 +0500 Subject: [PATCH 02/26] Work on polygons --- FlatCAMApp.py | 8 +++--- FlatCAMObj.py | 2 ++ VisPyCanvas.py | 9 ++++-- VisPyVisuals.py | 73 +++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 84 insertions(+), 8 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 62757ef..347688d 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -453,10 +453,10 @@ class App(QtCore.QObject): self.thr2 = QtCore.QThread() self.worker2.moveToThread(self.thr2) self.connect(self.thr2, QtCore.SIGNAL("started()"), self.worker2.run) - self.connect(self.thr2, QtCore.SIGNAL("started()"), - lambda: self.worker_task.emit({'fcn': self.version_check, - 'params': [], - 'worker_name': "worker2"})) + # self.connect(self.thr2, QtCore.SIGNAL("started()"), + # lambda: self.worker_task.emit({'fcn': self.version_check, + # 'params': [], + # 'worker_name': "worker2"})) self.thr2.start() ### Signal handling ### diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 8860bcb..94fcbcc 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -604,6 +604,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if self.options["solid"]: for poly in geometry: + self.app.plotcanvas.vispy_canvas.shapes.add(poly) # TODO: Too many things hardcoded. try: patch = PolygonPatch(poly, @@ -1472,6 +1473,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): return if type(element) == LineString or type(element) == LinearRing: + self.app.plotcanvas.vispy_canvas.shapes.add(element) x, y = element.coords.xy self.axes.plot(x, y, 'r-') return diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 6e66295..16e83cb 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -1,7 +1,7 @@ import numpy as np from PyQt4.QtGui import QPalette import vispy.scene as scene -from VisPyVisuals import LinesCollection, PolygonCollection +from VisPyVisuals import LinesCollection, PolygonCollection, ShapeCollection class VisPyCanvas(scene.SceneCanvas): @@ -37,7 +37,7 @@ class VisPyCanvas(scene.SceneCanvas): # -------------------------- Tests ---------------------------------------- - data1 = np.empty((4, 2)) + data1 = np.empty((5, 2)) data2 = np.empty((4, 2)) data1[0] = 0, -0.0 @@ -45,6 +45,9 @@ class VisPyCanvas(scene.SceneCanvas): data1[2] = 1, 1 data1[3] = 0, 1 + print "test", data1[:1] + print "test", np.any(data1[0] != data1[-1]) + data2[0] = 2, -0.0 data2[1] = 3, -0. data2[2] = 3, 1 @@ -84,6 +87,8 @@ class VisPyCanvas(scene.SceneCanvas): self.primitives['Line'] = LinesCollection(parent=view.scene) self.primitives['Poly'] = PolygonCollection(parent=view.scene) + self.shapes = ShapeCollection(parent=view.scene) + grid1 = scene.GridLines(parent=view.scene, color='gray') grid1.set_gl_state(depth_test=False) diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 640d603..255830e 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -1,8 +1,10 @@ -from vispy.visuals import LineVisual, PolygonVisual +from vispy.visuals import LineVisual, PolygonVisual, CompoundVisual, MeshVisual from vispy.scene.visuals import create_visual_node from vispy.geometry import PolygonData from vispy.gloo import set_state +from vispy.geometry.triangulation import Triangulation from matplotlib.colors import ColorConverter +from shapely.geometry import Polygon, LineString, LinearRing import numpy as np import abc @@ -43,6 +45,72 @@ class Collection: return data +class ShapeCollectionVisual(CompoundVisual): + def __init__(self, line_width=1, **kwargs): + self.shapes = {} + self.colors = {} + self.face_colors = {} + self.last_key = -1 + + self._mesh = MeshVisual() + self._lines = LineVisual() + self._line_width = line_width + + CompoundVisual.__init__(self, [self._mesh, self._lines], **kwargs) + self._mesh.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) + self.freeze() + + def add(self, shape, color=None, face_color=None, redraw=True): + self.last_key += 1 + self.shapes[self.last_key] = shape + self.colors[self.last_key] = color + self.face_colors[self.last_key] = face_color + if redraw: + self._update() + return self.last_key + + def remove(self, key, redraw=False): + del self.shapes[key] + del self.colors[key] + del self.face_colors[key] + if redraw: + self._update() + + def _update(self): + for shape in self.shapes.values(): + print 'shape type', type(shape) + if type(shape) == LineString: + pass + elif type(shape) == LinearRing: + pass + elif type(shape) == Polygon: + verts_m = self._open_ring(np.array(shape.simplify(0.01).exterior)) + edges_m = self._generate_edges(len(verts_m)) + + for ints in shape.interiors: + verts = self._open_ring(np.array(ints)) + edges_m = np.append(edges_m, self._generate_edges(len(verts)) + len(verts_m), 0) + verts_m = np.append(verts_m, verts, 0) + + print "verts, edges", verts_m, edges_m + + tri = Triangulation(verts_m, edges_m) + tri.triangulate() + + self._mesh.set_data(tri.pts, tri.tris, color='yellow') + + print "triangulation", tri.pts, tri.tris + + def _open_ring(self, vertices): + return vertices[:-1] if not np.any(vertices[0] != vertices[-1]) else vertices + + def _generate_edges(self, count): + edges = np.empty((count, 2), dtype=np.uint32) + edges[:, 0] = np.arange(count) + edges[:, 1] = edges[:, 0] + 1 + edges[-1, 1] = 0 + return edges + class LinesCollectionVisual(LineVisual, Collection): def __init__(self, width=1, method='gl', antialias=False): @@ -179,7 +247,7 @@ class PolygonCollectionVisual(PolygonVisual, Collection): def linearring_to_segments(arr): # Close linear ring - if np.any(arr[0] != arr[1]): + if np.any(arr[0] != arr[-1]): arr = np.concatenate([arr, arr[:1]], axis=0) return linestring_to_segments(arr) @@ -196,3 +264,4 @@ def linestring_to_segments(arr): LinesCollection = create_visual_node(LinesCollectionVisual) PolygonCollection = create_visual_node(PolygonCollectionVisual) +ShapeCollection = create_visual_node(ShapeCollectionVisual) From bbbc62cf5888e161444c54250563aee73959fc45 Mon Sep 17 00:00:00 2001 From: Denvi Date: Mon, 11 Jul 2016 23:59:11 +0500 Subject: [PATCH 03/26] FlatCAMGerber test --- FlatCAMObj.py | 1 + VisPyVisuals.py | 24 ++++++++++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 94fcbcc..e1a5c3a 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -624,6 +624,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): x, y = ints.coords.xy self.axes.plot(x, y, linespec) + self.app.plotcanvas.vispy_canvas.shapes.redraw() self.app.plotcanvas.auto_adjust_axes() def serialize(self): diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 255830e..02bd7ad 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -60,7 +60,7 @@ class ShapeCollectionVisual(CompoundVisual): self._mesh.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) self.freeze() - def add(self, shape, color=None, face_color=None, redraw=True): + def add(self, shape, color=None, face_color=None, redraw=False): self.last_key += 1 self.shapes[self.last_key] = shape self.colors[self.last_key] = color @@ -77,29 +77,33 @@ class ShapeCollectionVisual(CompoundVisual): self._update() def _update(self): + + pts_m = np.empty((0, 2)) # Vertices of triangles + tris_m = np.empty((0, 3)) # Indexes of vertices of faces + for shape in self.shapes.values(): - print 'shape type', type(shape) if type(shape) == LineString: pass elif type(shape) == LinearRing: pass elif type(shape) == Polygon: - verts_m = self._open_ring(np.array(shape.simplify(0.01).exterior)) + simple = shape.simplify(0.01) + verts_m = self._open_ring(np.array(simple.exterior)) edges_m = self._generate_edges(len(verts_m)) - for ints in shape.interiors: + for ints in simple.interiors: verts = self._open_ring(np.array(ints)) edges_m = np.append(edges_m, self._generate_edges(len(verts)) + len(verts_m), 0) verts_m = np.append(verts_m, verts, 0) - print "verts, edges", verts_m, edges_m - tri = Triangulation(verts_m, edges_m) tri.triangulate() - self._mesh.set_data(tri.pts, tri.tris, color='yellow') + tris_m = np.append(tris_m, tri.tris + len(pts_m), 0) + pts_m = np.append(pts_m, tri.pts, 0) - print "triangulation", tri.pts, tri.tris + if len(pts_m) > 0: + self._mesh.set_data(pts_m, tris_m.astype(np.uint32), color='red') def _open_ring(self, vertices): return vertices[:-1] if not np.any(vertices[0] != vertices[-1]) else vertices @@ -111,6 +115,10 @@ class ShapeCollectionVisual(CompoundVisual): edges[-1, 1] = 0 return edges + def redraw(self): + self._update() + + class LinesCollectionVisual(LineVisual, Collection): def __init__(self, width=1, method='gl', antialias=False): From 3a5723fe675fce8270eac81b398bca607b704cfc Mon Sep 17 00:00:00 2001 From: HDR Date: Tue, 12 Jul 2016 10:51:43 +0600 Subject: [PATCH 04/26] Work on ShapeCollection visual --- FlatCAMApp.py | 2 + FlatCAMObj.py | 25 +++- ObjectCollection.py | 1 + VisPyCanvas.py | 63 ++------- VisPyVisuals.py | 320 +++++++++++++------------------------------- 5 files changed, 128 insertions(+), 283 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 347688d..2f46117 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1521,6 +1521,8 @@ class App(QtCore.QObject): ymax += 0.05 * height self.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax) + self.plotcanvas.vispy_canvas.fit_view() + def on_key_over_plot(self, event): """ Callback for the key pressed event when the canvas is focused. Keyboard diff --git a/FlatCAMObj.py b/FlatCAMObj.py index e1a5c3a..c061c15 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -7,6 +7,7 @@ import inspect # TODO: For debugging only. from camlib import * from FlatCAMCommon import LoudDict from FlatCAMDraw import FlatCAMDraw +from VisPyVisuals import ShapeCollection ######################################## @@ -42,6 +43,8 @@ class FlatCAMObj(QtCore.QObject): self.axes = None # Matplotlib axes self.kind = None # Override with proper name + self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene) + self.muted_ui = False # assert isinstance(self.ui, ObjectUI) @@ -251,12 +254,17 @@ class FlatCAMObj(QtCore.QObject): if not self.options["plot"]: self.axes.cla() self.app.plotcanvas.auto_adjust_axes() + self.clear_shapes(update=True) return False # Clear axes or we will plot on top of them. self.axes.cla() # TODO: Thread safe? + self.clear_shapes() return True + def clear_shapes(self, update=False): + self.shapes.clear(update=update) + def serialize(self): """ Returns a representation of the object as a dictionary so @@ -604,7 +612,13 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if self.options["solid"]: for poly in geometry: - self.app.plotcanvas.vispy_canvas.shapes.add(poly) + if self.options["multicolored"]: + color = (np.random.sample(1), np.random.sample(1), np.random.sample(1), 0.75) + else: + color = '#BBF268BF' + + self.shapes.add(poly, color='#006E20BF', face_color=color) + # TODO: Too many things hardcoded. try: patch = PolygonPatch(poly, @@ -618,13 +632,20 @@ class FlatCAMGerber(FlatCAMObj, Gerber): FlatCAMApp.App.log.warning(str(poly)) else: for poly in geometry: + if self.options["multicolored"]: + color = (np.random.sample(1), np.random.sample(1), np.random.sample(1), 1.0) + else: + color = 'black' + + self.shapes.add(poly, color=color) + x, y = poly.exterior.xy self.axes.plot(x, y, linespec) for ints in poly.interiors: x, y = ints.coords.xy self.axes.plot(x, y, linespec) - self.app.plotcanvas.vispy_canvas.shapes.redraw() + self.shapes.redraw() self.app.plotcanvas.auto_adjust_axes() def serialize(self): diff --git a/ObjectCollection.py b/ObjectCollection.py index e423e5d..74125a5 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -214,6 +214,7 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginRemoveRows(QtCore.QModelIndex(), row, row) + self.object_list[row].clear_shapes(update=True) self.object_list.pop(row) self.endRemoveRows() diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 16e83cb..798b7f0 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -1,7 +1,7 @@ import numpy as np from PyQt4.QtGui import QPalette import vispy.scene as scene -from VisPyVisuals import LinesCollection, PolygonCollection, ShapeCollection +from vispy.geometry import Rect class VisPyCanvas(scene.SceneCanvas): @@ -35,65 +35,18 @@ class VisPyCanvas(scene.SceneCanvas): view = grid.add_view(row=1, col=1, border_color='black', bgcolor='white') view.camera = scene.PanZoomCamera(aspect=1) - # -------------------------- Tests ---------------------------------------- - - data1 = np.empty((5, 2)) - data2 = np.empty((4, 2)) - - data1[0] = 0, -0.0 - data1[1] = 1, -0. - data1[2] = 1, 1 - data1[3] = 0, 1 - - print "test", data1[:1] - print "test", np.any(data1[0] != data1[-1]) - - data2[0] = 2, -0.0 - data2[1] = 3, -0. - data2[2] = 3, 1 - data2[3] = 2, 1 - - # lines = LinesCollection(width=2) - # - # print "add1", lines.add(data1, color='blue') - # print "add2", lines.add(data2, color='green') - # - # lines.remove(0) - # # lines.remove(1) - # - # # view.add(lines) - # - # polys = PolygonCollection(border_width=2) - # polys.add(data1, color='yellow', border_color='red') - # polys.add(data2, color='green', border_color='blue') - # - # view.add(polys) - - # polys.remove(0) - # polys.remove(1) - - # ----------------------- End of tests ---------------------------- - - test = {'Poly': []} - - test['Poly'].append(3) - test['Poly'].append(5) - # test['Poly'] = np.append(test['Poly'], 1) - # test['Poly'] = np.append(test['Poly'], 4) - - print "test", test - - self.primitives = {} - self.primitives['Line'] = LinesCollection(parent=view.scene) - self.primitives['Poly'] = PolygonCollection(parent=view.scene) - - self.shapes = ShapeCollection(parent=view.scene) - grid1 = scene.GridLines(parent=view.scene, color='gray') grid1.set_gl_state(depth_test=False) xaxis.link_view(view) yaxis.link_view(view) + # self.shapes = ShapeCollection(parent=view.scene) self.view = view self.freeze() + + def fit_view(self): + rect = Rect() + rect.left, rect.right = self.view.get_scene_bounds(dim=0) + rect.bottom, rect.top = self.view.get_scene_bounds(dim=1) + self.view.camera.rect = rect diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 02bd7ad..572ef84 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -1,109 +1,114 @@ -from vispy.visuals import LineVisual, PolygonVisual, CompoundVisual, MeshVisual +from vispy.visuals import CompoundVisual, LineVisual, MeshVisual from vispy.scene.visuals import create_visual_node -from vispy.geometry import PolygonData from vispy.gloo import set_state from vispy.geometry.triangulation import Triangulation -from matplotlib.colors import ColorConverter +from vispy.color import Color from shapely.geometry import Polygon, LineString, LinearRing import numpy as np -import abc - - -class Collection: - - def __init__(self): - self.data = {} - self.last_key = -1 - self.colors = {} - self.border_colors = {} - - def add(self, data, form=None, color=None, border_color=None, update=False): - self.last_key += 1 - self.data[self.last_key] = self._translate_data(data, form) - self.colors[self.last_key] = color - self.border_colors[self.last_key] = border_color - if update: - self._update_data() - - return self.last_key - - def remove(self, key, update=False): - del self.data[key] - del self.colors[key] - del self.border_colors[key] - if update: - self._update_data() - - def redraw(self): - self._update_data() - - @abc.abstractmethod - def _update_data(self): - pass - - def _translate_data(self, data, form): - return data class ShapeCollectionVisual(CompoundVisual): def __init__(self, line_width=1, **kwargs): - self.shapes = {} - self.colors = {} - self.face_colors = {} + self.data = {} self.last_key = -1 self._mesh = MeshVisual() - self._lines = LineVisual() + self._line = LineVisual() self._line_width = line_width - CompoundVisual.__init__(self, [self._mesh, self._lines], **kwargs) + CompoundVisual.__init__(self, [self._mesh, self._line], **kwargs) self._mesh.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) self.freeze() - def add(self, shape, color=None, face_color=None, redraw=False): + def add(self, shape, color=None, face_color=None, update=False): self.last_key += 1 - self.shapes[self.last_key] = shape - self.colors[self.last_key] = color - self.face_colors[self.last_key] = face_color - if redraw: + self.data[self.last_key] = shape, color, face_color + if update: self._update() return self.last_key - def remove(self, key, redraw=False): - del self.shapes[key] - del self.colors[key] - del self.face_colors[key] - if redraw: + def remove(self, key, update=False): + del self.data[key] + if update: + self._update() + + def clear(self, update=False): + self.data = {} + if update: self._update() def _update(self): + mesh_vertices = np.empty((0, 2)) # Vertices for mesh + mesh_tris = np.empty((0, 3)) # Faces for mesh + mesh_colors = np.empty((0, 4)) # Face colors - pts_m = np.empty((0, 2)) # Vertices of triangles - tris_m = np.empty((0, 3)) # Indexes of vertices of faces + line_pts = np.empty((0, 2)) # Vertices for line + line_colors = np.empty((0, 4)) # Line color + + # Creating arrays for mesh and line from all shapes + for shape, color, face_color in self.data.values(): + + simple = shape.simplify(0.01) # Simplified shape + pts = np.empty((0, 2)) # Shape line points + tri_pts = np.empty((0, 2)) # Mesh vertices + tri_tris = np.empty((0, 3)) # Mesh faces - for shape in self.shapes.values(): if type(shape) == LineString: - pass + # Prepare lines + pts = self._linestring_to_segments(np.array(simple.exterior)) + elif type(shape) == LinearRing: - pass + # Prepare lines + pts = self._linearring_to_segments(np.array(simple.exterior)) + elif type(shape) == Polygon: - simple = shape.simplify(0.01) - verts_m = self._open_ring(np.array(simple.exterior)) - edges_m = self._generate_edges(len(verts_m)) + # Prepare polygon faces + if face_color is not None: + # Concatenated arrays of external & internal line rings + vertices = self._open_ring(np.array(simple.exterior)) + edges = self._generate_edges(len(vertices)) - for ints in simple.interiors: - verts = self._open_ring(np.array(ints)) - edges_m = np.append(edges_m, self._generate_edges(len(verts)) + len(verts_m), 0) - verts_m = np.append(verts_m, verts, 0) + for ints in simple.interiors: + v = self._open_ring(np.array(ints)) + edges = np.append(edges, self._generate_edges(len(v)) + len(vertices), 0) + vertices = np.append(vertices, v, 0) - tri = Triangulation(verts_m, edges_m) - tri.triangulate() + tri = Triangulation(vertices, edges) + tri.triangulate() + tri_pts, tri_tris = tri.pts, tri.tris - tris_m = np.append(tris_m, tri.tris + len(pts_m), 0) - pts_m = np.append(pts_m, tri.pts, 0) + # Prepare polygon edges + if color is not None: + pts = self._linearring_to_segments(np.array(simple.exterior)) + for ints in simple.interiors: + pts = np.append(pts, self._linearring_to_segments(np.array(ints)), 0) - if len(pts_m) > 0: - self._mesh.set_data(pts_m, tris_m.astype(np.uint32), color='red') + # Appending data for mesh + if len(tri_pts) > 0 and len(tri_tris) > 0: + mesh_tris = np.append(mesh_tris, tri_tris + len(mesh_vertices), 0) + mesh_vertices = np.append(mesh_vertices, tri_pts, 0) + mesh_colors = np.append(mesh_colors, np.full((len(tri_tris), 4), Color(face_color).rgba), 0) + + # Appending data for line + if len(pts) > 0: + line_pts = np.append(line_pts, pts, 0) + line_colors = np.append(line_colors, np.full((len(pts), 4), Color(color).rgba), 0) + + # Updating mesh + if len(mesh_vertices) > 0: + set_state(polygon_offset_fill=False) + self._mesh.set_data(mesh_vertices, mesh_tris.astype(np.uint32), face_colors=mesh_colors) + else: + self._mesh.set_data() + + # Updating line + if len(line_pts) > 0: + self._line.set_data(line_pts, line_colors, self._line_width, 'segments') + else: + self._line._bounds = None + self._line._pos = None + self._line._changed['pos'] = True + self._line.update() def _open_ring(self, vertices): return vertices[:-1] if not np.any(vertices[0] != vertices[-1]) else vertices @@ -115,161 +120,24 @@ class ShapeCollectionVisual(CompoundVisual): edges[-1, 1] = 0 return edges + def _linearring_to_segments(self, arr): + + # Close linear ring + if np.any(arr[0] != arr[-1]): + arr = np.concatenate([arr, arr[:1]], axis=0) + + return self._linestring_to_segments(arr) + + def _linestring_to_segments(self, arr): + lines = [] + for pnt in range(0, len(arr) - 1): + lines.append(arr[pnt]) + lines.append(arr[pnt + 1]) + + return np.array(lines) + def redraw(self): self._update() -class LinesCollectionVisual(LineVisual, Collection): - - def __init__(self, width=1, method='gl', antialias=False): - self.color_converter = ColorConverter() - Collection.__init__(self) - LineVisual.__init__(self, width=width, connect='segments', method=method, antialias=antialias) - - def _update_data(self): - if len(self.data) > 0: - # Merge arrays of segments, colors of segments - pos_m = np.empty((0, 2)) # Line segments - color_m = np.empty((0, 4)) # Colors of segments - - for key in self.data: - if self.colors[key] is None: - continue - pos = self.data[key] - color = np.full((len(pos), 4), self.color_converter.to_rgba(self.colors[key])) - pos_m = np.append(pos_m, pos, 0) - color_m = np.append(color_m, color, 0) - - # Update lines - if len(pos_m) > 0: - LineVisual.set_data(self, pos_m, color=color_m) - - else: - self._bounds = None - self._pos = None - self._changed['pos'] = True - self.update() - - def _translate_data(self, data, form): - if form == 'LineString': - return linestring_to_segments(data) - elif form == 'LinearRing': - return linearring_to_segments(data) - else: - return data - - def set_data(self, pos=None, color=None, width=None, connect=None): - pass - - -class PolygonCollectionVisual(PolygonVisual, Collection): - - def __init__(self, color='black', border_width=1, **kwargs): - self.color_converter = ColorConverter() - Collection.__init__(self) - PolygonVisual.__init__(self, border_width=border_width, **kwargs) - - def _update_data(self): - if len(self.data) > 0: - # Merge arrays of vertices, faces, face colors - pts_m = np.empty((0, 2)) # Vertices of triangles - tris_m = np.empty((0, 3)) # Indexes of vertices of faces - colors_m = np.empty((0, 4)) # Colors of faces - - for key in self.data: - if self.colors[key] is None: - continue - pos = self.data[key] - data = PolygonData(vertices=np.array(pos, dtype=np.float32)) - - pts, tris = data.triangulate() - offset = np.full((1, 3), len(pts_m)) - tris = np.add(tris, offset) - - pts_m, tris_m = np.append(pts_m, pts, 0), np.append(tris_m, tris, 0) - - colors = np.full((len(tris), 4), self.color_converter.to_rgba(self.colors[key])) - colors_m = np.append(colors_m, colors, 0) - - # Update mesh - if len(pts_m) > 0: - set_state(polygon_offset_fill=False) - self._mesh.set_data(vertices=pts_m, faces=tris_m.astype(np.uint32), - face_colors=colors_m) - - # Merge arrays of segments, colors of segments - pos_m = np.empty((0, 2)) # Line segments of borders - border_colors_m = np.empty((0, 4)) # Colors of segments - - for key in self.data: - if self.border_colors[key] is None: - continue - pos = self.data[key] - pos = linearring_to_segments(pos) - pos_m = np.append(pos_m, pos, 0) - - border_colors = np.full((len(pos), 4), self.color_converter.to_rgba(self.border_colors[key])) - border_colors_m = np.append(border_colors_m, border_colors, 0) - - # Update borders - if len(pos_m) > 0: - self._border.set_data(pos=pos_m, color=border_colors_m, width=self._border_width, - connect='segments') - self._border.update() - - else: - self._mesh.set_data() - self._border._bounds = None - self._border._pos = None - self._border._changed['pos'] = True - self._border.update() - - def _update(self): - self._update_data() - - @property - def pos(self): - return None - - @pos.setter - def pos(self, pos): - pass - - @property - def color(self): - return None - - @color.setter - def color(self, color): - pass - - @property - def border_color(self): - return None - - @border_color.setter - def border_color(self, border_color): - pass - - -def linearring_to_segments(arr): - - # Close linear ring - if np.any(arr[0] != arr[-1]): - arr = np.concatenate([arr, arr[:1]], axis=0) - - return linestring_to_segments(arr) - - -def linestring_to_segments(arr): - lines = [] - for pnt in range(0, len(arr) - 1): - lines.append(arr[pnt]) - lines.append(arr[pnt + 1]) - - return np.array(lines) - - -LinesCollection = create_visual_node(LinesCollectionVisual) -PolygonCollection = create_visual_node(PolygonCollectionVisual) ShapeCollection = create_visual_node(ShapeCollectionVisual) From 0378d1772912fe03f3879d36629259e08bd3a7c3 Mon Sep 17 00:00:00 2001 From: HDR Date: Tue, 12 Jul 2016 11:19:54 +0600 Subject: [PATCH 05/26] Geometry, Excellon objects translated to VisPy --- FlatCAMObj.py | 99 ++++++++++++++++++++++++--------------------- ObjectCollection.py | 3 ++ VisPyVisuals.py | 4 +- camlib.py | 6 +-- svgparse.py | 2 +- 5 files changed, 63 insertions(+), 51 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index c061c15..d39ca48 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -619,17 +619,17 @@ class FlatCAMGerber(FlatCAMObj, Gerber): self.shapes.add(poly, color='#006E20BF', face_color=color) - # TODO: Too many things hardcoded. - try: - patch = PolygonPatch(poly, - facecolor="#BBF268", - edgecolor="#006E20", - alpha=0.75, - zorder=2) - self.axes.add_patch(patch) - except AssertionError: - FlatCAMApp.App.log.warning("A geometry component was not a polygon:") - FlatCAMApp.App.log.warning(str(poly)) + # # TODO: Too many things hardcoded. + # try: + # patch = PolygonPatch(poly, + # facecolor="#BBF268", + # edgecolor="#006E20", + # alpha=0.75, + # zorder=2) + # self.axes.add_patch(patch) + # except AssertionError: + # FlatCAMApp.App.log.warning("A geometry component was not a polygon:") + # FlatCAMApp.App.log.warning(str(poly)) else: for poly in geometry: if self.options["multicolored"]: @@ -639,14 +639,14 @@ class FlatCAMGerber(FlatCAMObj, Gerber): self.shapes.add(poly, color=color) - x, y = poly.exterior.xy - self.axes.plot(x, y, linespec) - for ints in poly.interiors: - x, y = ints.coords.xy - self.axes.plot(x, y, linespec) + # x, y = poly.exterior.xy + # self.axes.plot(x, y, linespec) + # for ints in poly.interiors: + # x, y = ints.coords.xy + # self.axes.plot(x, y, linespec) self.shapes.redraw() - self.app.plotcanvas.auto_adjust_axes() + # self.app.plotcanvas.auto_adjust_axes() def serialize(self): return { @@ -980,21 +980,27 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # Plot excellon (All polygons?) if self.options["solid"]: for geo in self.solid_geometry: - patch = PolygonPatch(geo, - facecolor="#C40000", - edgecolor="#750000", - alpha=0.75, - zorder=3) - self.axes.add_patch(patch) + self.shapes.add(geo, color='#750000BF', face_color='#C40000BF') + + # patch = PolygonPatch(geo, + # facecolor="#C40000", + # edgecolor="#750000", + # alpha=0.75, + # zorder=3) + # self.axes.add_patch(patch) else: for geo in self.solid_geometry: - x, y = geo.exterior.coords.xy - self.axes.plot(x, y, 'r-') - for ints in geo.interiors: - x, y = ints.coords.xy - self.axes.plot(x, y, 'g-') + self.shapes.add(geo.exterior, color='red') - self.app.plotcanvas.auto_adjust_axes() + # x, y = geo.exterior.coords.xy + # self.axes.plot(x, y, 'r-') + for ints in geo.interiors: + self.shapes.add(ints, color='green') + # x, y = ints.coords.xy + # self.axes.plot(x, y, 'g-') + + self.shapes.redraw() + # self.app.plotcanvas.auto_adjust_axes() class FlatCAMCNCjob(FlatCAMObj, CNCjob): @@ -1153,7 +1159,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if not FlatCAMObj.plot(self): return - self.plot2(self.axes, tooldia=self.options["tooldia"]) + self.plot2(self.axes, tooldia=self.options["tooldia"], shapes=self.shapes) self.app.plotcanvas.auto_adjust_axes() @@ -1486,21 +1492,23 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): except TypeError: # Element is not iterable... - if type(element) == Polygon: - x, y = element.exterior.coords.xy - self.axes.plot(x, y, 'r-') - for ints in element.interiors: - x, y = ints.coords.xy - self.axes.plot(x, y, 'r-') - return + self.shapes.add(element, color='red') - if type(element) == LineString or type(element) == LinearRing: - self.app.plotcanvas.vispy_canvas.shapes.add(element) - x, y = element.coords.xy - self.axes.plot(x, y, 'r-') - return - - FlatCAMApp.App.log.warning("Did not plot:" + str(type(element))) + # if type(element) == Polygon: + # x, y = element.exterior.coords.xy + # self.axes.plot(x, y, 'r-') + # for ints in element.interiors: + # x, y = ints.coords.xy + # self.axes.plot(x, y, 'r-') + # return + # + # if type(element) == LineString or type(element) == LinearRing: + # self.app.plotcanvas.vispy_canvas.shapes.add(element) + # x, y = element.coords.xy + # self.axes.plot(x, y, 'r-') + # return + # + # FlatCAMApp.App.log.warning("Did not plot:" + str(type(element))) def plot(self): """ @@ -1552,5 +1560,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): # FlatCAMApp.App.log.warning("Did not plot:", str(type(geo))) self.plot_element(self.solid_geometry) + self.shapes.redraw() - self.app.plotcanvas.auto_adjust_axes() + # self.app.plotcanvas.auto_adjust_axes() diff --git a/ObjectCollection.py b/ObjectCollection.py index 74125a5..d98673b 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -296,6 +296,9 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginResetModel() + for obj in self.object_list: + obj.clear_shapes(update=True) + self.object_list = [] self.checked_indexes = [] diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 572ef84..2837d1f 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -55,11 +55,11 @@ class ShapeCollectionVisual(CompoundVisual): if type(shape) == LineString: # Prepare lines - pts = self._linestring_to_segments(np.array(simple.exterior)) + pts = self._linestring_to_segments(np.array(simple)) elif type(shape) == LinearRing: # Prepare lines - pts = self._linearring_to_segments(np.array(simple.exterior)) + pts = self._linearring_to_segments(np.array(simple)) elif type(shape) == Polygon: # Prepare polygon faces diff --git a/camlib.py b/camlib.py index 2e7a82d..cd0818b 100644 --- a/camlib.py +++ b/camlib.py @@ -45,11 +45,11 @@ import simplejson as json #from matplotlib.pyplot import plot, subplot import xml.etree.ElementTree as ET -from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path +# from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path import itertools import xml.etree.ElementTree as ET -from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path +# from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path from svgparse import * @@ -3245,7 +3245,7 @@ class CNCjob(Geometry): def plot2(self, axes, tooldia=None, dpi=75, margin=0.1, color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]}, - alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005): + alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, shapes=None): """ Plots the G-code job onto the given axes. diff --git a/svgparse.py b/svgparse.py index 544f0ba..fd88c8e 100644 --- a/svgparse.py +++ b/svgparse.py @@ -22,7 +22,7 @@ import xml.etree.ElementTree as ET import re import itertools -from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path +# from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path from shapely.geometry import LinearRing, LineString, Point from shapely.affinity import translate, rotate, scale, skew, affine_transform import numpy as np From c5eb35d86a639c7c5d73755cc3bf9613c8f26c76 Mon Sep 17 00:00:00 2001 From: HDR Date: Tue, 12 Jul 2016 16:06:55 +0600 Subject: [PATCH 06/26] Objects plotted only once on project open now. --- FlatCAMApp.py | 9 +++++---- FlatCAMObj.py | 3 ++- ObjectCollection.py | 2 ++ PlotCanvas.py | 1 + VisPyCanvas.py | 10 +++++++--- VisPyVisuals.py | 32 +++++++++++++++++++++++++------- camlib.py | 34 +++++++++++++++++++--------------- 7 files changed, 61 insertions(+), 30 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 2f46117..813a8b4 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -100,7 +100,7 @@ class App(QtCore.QObject): # Emitted by new_object() and passes the new object as argument. # on_object_created() adds the object to the collection, # and emits new_object_available. - object_created = QtCore.pyqtSignal(object) + object_created = QtCore.pyqtSignal(object, bool) # Emitted when a new object has been added to the collection # and is ready to be used. @@ -1027,7 +1027,7 @@ class App(QtCore.QObject): # Move the object to the main thread and let the app know that it is available. obj.moveToThread(QtGui.QApplication.instance().thread()) - self.object_created.emit(obj) + self.object_created.emit(obj, plot) return obj @@ -1482,7 +1482,7 @@ class App(QtCore.QObject): def on_row_activated(self, index): self.ui.notebook.setCurrentWidget(self.ui.selected_tab) - def on_object_created(self, obj): + def on_object_created(self, obj, plot): """ Event callback for object creation. @@ -1497,7 +1497,8 @@ class App(QtCore.QObject): self.inform.emit("Object (%s) created: %s" % (obj.kind, obj.options['name'])) self.new_object_available.emit(obj) - obj.plot() + if plot: + obj.plot() self.on_zoom_fit(None) t1 = time.time() # DEBUG self.log.debug("%f seconds adding object and plotting." % (t1 - t0)) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index d39ca48..9f3841a 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1161,7 +1161,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): self.plot2(self.axes, tooldia=self.options["tooldia"], shapes=self.shapes) - self.app.plotcanvas.auto_adjust_axes() + self.shapes.redraw() + # self.app.plotcanvas.auto_adjust_axes() def convert_units(self, units): factor = CNCjob.convert_units(self, units) diff --git a/ObjectCollection.py b/ObjectCollection.py index d98673b..b26993a 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -215,6 +215,8 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginRemoveRows(QtCore.QModelIndex(), row, row) self.object_list[row].clear_shapes(update=True) + # del self.object_list[row].shapes # TODO: Check object deletion + # self.object_list[row].shapes = None self.object_list.pop(row) self.endRemoveRows() diff --git a/PlotCanvas.py b/PlotCanvas.py index cdf423e..e14d536 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -152,6 +152,7 @@ class PlotCanvas(QtCore.QObject): # Attach to parent #self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns?? # self.container.addWidget(self.canvas) # Qt + conf = {'samples': 8} self.vispy_canvas = VisPyCanvas() self.vispy_canvas.create_native() self.vispy_canvas.native.setParent(self.app.ui) diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 798b7f0..ec864f1 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -6,8 +6,8 @@ from vispy.geometry import Rect class VisPyCanvas(scene.SceneCanvas): - def __init__(self): - scene.SceneCanvas.__init__(self, keys=None) + def __init__(self, config=None): + scene.SceneCanvas.__init__(self, keys=None, config=config) self.unfreeze() back_color = str(QPalette().color(QPalette.Window).name()) @@ -41,7 +41,11 @@ class VisPyCanvas(scene.SceneCanvas): xaxis.link_view(view) yaxis.link_view(view) - # self.shapes = ShapeCollection(parent=view.scene) + # shapes = scene.Line(parent=view.scene) + # view.add(shapes) + + print "config", self.context.config + self.view = view self.freeze() diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 2837d1f..5f0c731 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -13,14 +13,33 @@ class ShapeCollectionVisual(CompoundVisual): self.last_key = -1 self._mesh = MeshVisual() - self._line = LineVisual() + self._line = LineVisual(antialias=True) self._line_width = line_width CompoundVisual.__init__(self, [self._mesh, self._line], **kwargs) self._mesh.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) + self._line.set_gl_state(blend=True) self.freeze() + def __del__(self): + print "ShapeCollection destructed" + def add(self, shape, color=None, face_color=None, update=False): + """Adds geometry object to collection + + Args: + shape: shapely.geometry + Shapely geometry object + color: tuple + Line (polygon edge) color + face_color: tuple + Polygon fill color + update: bool + Set to redraw collection + + Returns: int + + """ self.last_key += 1 self.data[self.last_key] = shape, color, face_color if update: @@ -48,7 +67,7 @@ class ShapeCollectionVisual(CompoundVisual): # Creating arrays for mesh and line from all shapes for shape, color, face_color in self.data.values(): - simple = shape.simplify(0.01) # Simplified shape + simple = shape.simplify(0.01, preserve_topology=False) # Simplified shape pts = np.empty((0, 2)) # Shape line points tri_pts = np.empty((0, 2)) # Mesh vertices tri_tris = np.empty((0, 3)) # Mesh faces @@ -103,6 +122,7 @@ class ShapeCollectionVisual(CompoundVisual): # Updating line if len(line_pts) > 0: + set_state(blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self._line.set_data(line_pts, line_colors, self._line_width, 'segments') else: self._line._bounds = None @@ -129,12 +149,10 @@ class ShapeCollectionVisual(CompoundVisual): return self._linestring_to_segments(arr) def _linestring_to_segments(self, arr): - lines = [] - for pnt in range(0, len(arr) - 1): - lines.append(arr[pnt]) - lines.append(arr[pnt + 1]) + return np.array(np.repeat(arr, 2, axis=0)[1:-1]) - return np.array(lines) + def _compute_bounds(self, axis, view): + return self._line._compute_bounds(axis, view) def redraw(self): self._update() diff --git a/camlib.py b/camlib.py index cd0818b..c781e42 100644 --- a/camlib.py +++ b/camlib.py @@ -3244,7 +3244,7 @@ class CNCjob(Geometry): # return fig def plot2(self, axes, tooldia=None, dpi=75, margin=0.1, - color={"T": ["#F0E24D", "#B5AB3A"], "C": ["#5E6CFF", "#4650BD"]}, + color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]}, alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, shapes=None): """ Plots the G-code job onto the given axes. @@ -3265,24 +3265,28 @@ class CNCjob(Geometry): if tooldia == 0: for geo in self.gcode_parsed: - linespec = '--' + # linespec = '--' linecolor = color[geo['kind'][0]][1] - if geo['kind'][0] == 'C': - linespec = 'k-' - x, y = geo['geom'].coords.xy - axes.plot(x, y, linespec, color=linecolor) + # if geo['kind'][0] == 'C': + # linespec = 'k-' + # x, y = geo['geom'].coords.xy + # axes.plot(x, y, linespec, color=linecolor) + shapes.add(geo['geom'], color=linecolor) else: for geo in self.gcode_parsed: - path_num += 1 - axes.annotate(str(path_num), xy=geo['geom'].coords[0], - xycoords='data') + # path_num += 1 + # axes.annotate(str(path_num), xy=geo['geom'].coords[0], + # xycoords='data') + + if geo['kind'][0] == 'C': + poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance) + # patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0], + # edgecolor=color[geo['kind'][0]][1], + # alpha=alpha[geo['kind'][0]], zorder=2) + # axes.add_patch(patch) + # shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) + shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) - poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance) - patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0], - edgecolor=color[geo['kind'][0]][1], - alpha=alpha[geo['kind'][0]], zorder=2) - axes.add_patch(patch) - def create_geometry(self): # TODO: This takes forever. Too much data? self.solid_geometry = cascaded_union([geo['geom'] for geo in self.gcode_parsed]) From bb814c1ac1bbeabf216a2299e83466064d036df2 Mon Sep 17 00:00:00 2001 From: Denvi Date: Tue, 12 Jul 2016 23:24:21 +0500 Subject: [PATCH 07/26] GPC triangulation --- FlatCAMApp.py | 4 +-- ObjectCollection.py | 6 ++--- VisPyVisuals.py | 60 ++++++++++++++++++++++++++++++++++----------- 3 files changed, 50 insertions(+), 20 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 813a8b4..fc1d739 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -2200,9 +2200,9 @@ class App(QtCore.QObject): def obj_init(obj_inst, app_inst): obj_inst.from_dict(obj) App.log.debug(obj['kind'] + ": " + obj['options']['name']) - self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=False) + self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True) - self.plot_all() + # self.plot_all() self.inform.emit("Project loaded from: " + filename) App.log.debug("Project loaded") diff --git a/ObjectCollection.py b/ObjectCollection.py index b26993a..d47c979 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -214,9 +214,7 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginRemoveRows(QtCore.QModelIndex(), row, row) - self.object_list[row].clear_shapes(update=True) - # del self.object_list[row].shapes # TODO: Check object deletion - # self.object_list[row].shapes = None + self.object_list[row].shapes.parent = None self.object_list.pop(row) self.endRemoveRows() @@ -299,7 +297,7 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginResetModel() for obj in self.object_list: - obj.clear_shapes(update=True) + obj.shapes.parent = None self.object_list = [] self.checked_indexes = [] diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 5f0c731..ec1c803 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -5,6 +5,8 @@ from vispy.geometry.triangulation import Triangulation from vispy.color import Color from shapely.geometry import Polygon, LineString, LinearRing import numpy as np +from shapely.ops import triangulate +import Polygon as gpc class ShapeCollectionVisual(CompoundVisual): @@ -67,40 +69,65 @@ class ShapeCollectionVisual(CompoundVisual): # Creating arrays for mesh and line from all shapes for shape, color, face_color in self.data.values(): - simple = shape.simplify(0.01, preserve_topology=False) # Simplified shape + simple = shape.simplify(0.01) # Simplified shape pts = np.empty((0, 2)) # Shape line points tri_pts = np.empty((0, 2)) # Mesh vertices tri_tris = np.empty((0, 3)) # Mesh faces if type(shape) == LineString: # Prepare lines - pts = self._linestring_to_segments(np.array(simple)) + pts = self._linestring_to_segments(np.asarray(simple)) elif type(shape) == LinearRing: # Prepare lines - pts = self._linearring_to_segments(np.array(simple)) + pts = self._linearring_to_segments(np.asarray(simple)) elif type(shape) == Polygon: # Prepare polygon faces if face_color is not None: # Concatenated arrays of external & internal line rings - vertices = self._open_ring(np.array(simple.exterior)) - edges = self._generate_edges(len(vertices)) + # vertices = self._open_ring(np.array(simple.exterior)) + # edges = self._generate_edges(len(vertices)) + # + # print "poly exterior pts:", len(vertices) + # + # for ints in simple.interiors: + # v = self._open_ring(np.array(ints)) + # edges = np.append(edges, self._generate_edges(len(v)) + len(vertices), 0) + # vertices = np.append(vertices, v, 0) + # + # print "poly interior pts:", len(v) + # + # tri = Triangulation(vertices, edges) + # tri.triangulate() + # tri_pts, tri_tris = tri.pts, tri.tris + + # Shapely triangulation + # tri_pts = np.array(map(lambda x: np.array(x.exterior)[:-1], triangulate(shape))).reshape(-1, 2) + # tri_tris = np.arange(0, len(tri_pts), dtype=np.uint32).reshape((-1, 3)) + + p = gpc.Polygon(np.asarray(simple.exterior)) for ints in simple.interiors: - v = self._open_ring(np.array(ints)) - edges = np.append(edges, self._generate_edges(len(v)) + len(vertices), 0) - vertices = np.append(vertices, v, 0) + q = gpc.Polygon(np.asarray(ints)) + p -= q - tri = Triangulation(vertices, edges) - tri.triangulate() - tri_pts, tri_tris = tri.pts, tri.tris + tri_pts = np.empty((0, 2)) + tri_tris = np.empty((0, 3)) + + for strip in p.triStrip(): + a = np.repeat(np.arange(0, len(strip) - 2), 3).reshape((-1, 3)) + a[:, 1] += 1 + a[:, 2] += 2 + + tri_tris = np.append(tri_tris, a + len(tri_pts), 0) + tri_pts = np.append(tri_pts, np.asarray(strip), 0) # Prepare polygon edges if color is not None: - pts = self._linearring_to_segments(np.array(simple.exterior)) + pts = self._linearring_to_segments(np.asarray(simple.exterior)) for ints in simple.interiors: - pts = np.append(pts, self._linearring_to_segments(np.array(ints)), 0) + pts = np.append(pts, self._linearring_to_segments(np.asarray(ints)), 0) # Appending data for mesh if len(tri_pts) > 0 and len(tri_tris) > 0: @@ -108,6 +135,11 @@ class ShapeCollectionVisual(CompoundVisual): mesh_vertices = np.append(mesh_vertices, tri_pts, 0) mesh_colors = np.append(mesh_colors, np.full((len(tri_tris), 4), Color(face_color).rgba), 0) + # Random face colors + # rc = np.random.rand(len(tri_tris), 4) + # rc[:, 3] = 1.0 + # mesh_colors = np.append(mesh_colors, rc, 0) + # Appending data for line if len(pts) > 0: line_pts = np.append(line_pts, pts, 0) @@ -149,7 +181,7 @@ class ShapeCollectionVisual(CompoundVisual): return self._linestring_to_segments(arr) def _linestring_to_segments(self, arr): - return np.array(np.repeat(arr, 2, axis=0)[1:-1]) + return np.asarray(np.repeat(arr, 2, axis=0)[1:-1]) def _compute_bounds(self, axis, view): return self._line._compute_bounds(axis, view) From 45e2404eaa16a176c05d54ce6436c6ac699c68c4 Mon Sep 17 00:00:00 2001 From: Denvi Date: Wed, 13 Jul 2016 19:58:00 +0500 Subject: [PATCH 08/26] Work on geometry editor. Performance test. --- FlatCAMApp.py | 24 ++++++++++++ FlatCAMDraw.py | 62 +++++++++++++++++++++++-------- FlatCAMObj.py | 7 ++-- PlotCanvas.py | 4 ++ VisPyCanvas.py | 12 +++++- VisPyVisuals.py | 97 ++++++++++++++++++++++++++++++------------------- 6 files changed, 148 insertions(+), 58 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index fc1d739..a372c33 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -192,6 +192,8 @@ class App(QtCore.QObject): self.plotcanvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot) self.plotcanvas.mpl_connect('key_press_event', self.on_key_over_plot) + self.plotcanvas.vis_connect('mouse_move', self.on_mouse_move) + self.ui.splitter.setStretchFactor(1, 2) ############## @@ -1608,6 +1610,27 @@ class App(QtCore.QObject): self.ui.position_label.setText("") self.mouse = None + def on_mouse_move(self, event): + """ + Callback for the mouse motion event over the plot. This event is generated + by the Matplotlib backend and has been registered in ``self.__init__()``. + For details, see: http://matplotlib.org/users/event_handling.html + + :param event: Contains information about the event. + :return: None + """ + + pos = self.plotcanvas.vispy_canvas.translate_coords(event.pos) + + try: # May fail in case mouse not within axes + self.ui.position_label.setText("X: %.4f Y: %.4f" % ( + pos[0], pos[1])) + self.mouse = [pos[0], pos[1]] + + except: + self.ui.position_label.setText("") + self.mouse = None + def on_file_new(self): """ Callback for menu item File->New. Returns the application to its @@ -2202,6 +2225,7 @@ class App(QtCore.QObject): App.log.debug(obj['kind'] + ": " + obj['options']['name']) self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True) + self.plotcanvas.vispy_canvas.shapes.redraw() # self.plot_all() self.inform.emit("Project loaded from: " + filename) App.log.debug("Project loaded") diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index fbfdc05..5a340ec 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -19,7 +19,7 @@ from numpy.linalg import solve #from mpl_toolkits.axes_grid.anchored_artists import AnchoredDrawingArea from rtree import index as rtindex - +from VisPyVisuals import ShapeCollection class BufferSelectionTool(FlatCAMTool): """ @@ -697,6 +697,8 @@ class FlatCAMDraw(QtCore.QObject): self.active_tool = None self.storage = FlatCAMDraw.make_storage() + self.shapes = ShapeCollection() + self.tool_shape = ShapeCollection() self.utility = [] ## List of selected shapes. @@ -749,7 +751,8 @@ class FlatCAMDraw(QtCore.QObject): self.snap_max_dist_entry.editingFinished.connect(lambda: entry2option("snap_max", self.snap_max_dist_entry)) def activate(self): - pass + self.shapes.parent = self.canvas.vispy_canvas.view.scene + self.tool_shape.parent = self.canvas.vispy_canvas.view.scene def connect_canvas_event_handlers(self): ## Canvas events @@ -758,6 +761,9 @@ class FlatCAMDraw(QtCore.QObject): self.cid_canvas_key = self.canvas.mpl_connect('key_press_event', self.on_canvas_key) self.cid_canvas_key_release = self.canvas.mpl_connect('key_release_event', self.on_canvas_key_release) + self.canvas.vis_connect('mouse_release', self.on_canvas_click) + self.canvas.vis_connect('mouse_move', self.on_canvas_move) + def disconnect_canvas_event_handlers(self): self.canvas.mpl_disconnect(self.cid_canvas_click) self.canvas.mpl_disconnect(self.cid_canvas_move) @@ -799,6 +805,8 @@ class FlatCAMDraw(QtCore.QObject): self.clear() self.drawing_toolbar.setDisabled(True) self.snap_toolbar.setDisabled(True) # TODO: Combine and move into tool + self.shapes.parent = None + self.tool_shape.parent = None def delete_utility_geometry(self): #for_deletion = [shape for shape in self.shape_buffer if shape.utility] @@ -847,6 +855,7 @@ class FlatCAMDraw(QtCore.QObject): "Expected a Geometry, got %s" % type(fcgeometry) self.deactivate() + self.activate() self.connect_canvas_event_handlers() self.select_tool("select") @@ -896,10 +905,14 @@ class FlatCAMDraw(QtCore.QObject): :param event: Event object dispatched by Matplotlib :return: None """ + + pos = self.canvas.vispy_canvas.translate_coords(event.pos) + # Selection with left mouse button if self.active_tool is not None and event.button is 1: # Dispatch event to active_tool - msg = self.active_tool.click(self.snap(event.xdata, event.ydata)) + # msg = self.active_tool.click(self.snap(event.xdata, event.ydata)) + msg = self.active_tool.click(self.snap(pos[0], pos[1])) self.app.info(msg) # If it is a shape generating tool @@ -921,6 +934,10 @@ class FlatCAMDraw(QtCore.QObject): :param event: Event object dispatched by Matplotlib :return: """ + + pos = self.canvas.vispy_canvas.translate_coords(event.pos) + event.xdata, event.ydata = pos[0], pos[1] + self.on_canvas_move_effective(event) return None @@ -970,30 +987,38 @@ class FlatCAMDraw(QtCore.QObject): geo = self.active_tool.utility_geometry(data=(x, y)) if isinstance(geo, DrawToolShape) and geo.geo is not None: - # Remove any previous utility shape - self.delete_utility_geometry() + # self.delete_utility_geometry() + self.tool_shape.clear(update=True) # Add the new utility shape - self.add_shape(geo) + # self.add_shape(geo) # Efficient plotting for fast animation #self.canvas.canvas.restore_region(self.canvas.background) - elements = self.plot_shape(geometry=geo.geo, - linespec="b--", - linewidth=1, - animated=True) - for el in elements: - self.axes.draw_artist(el) + # elements = self.plot_shape(geometry=geo.geo, + # linespec="b--", + # linewidth=1, + # animated=True) + try: + for el in list(geo.geo): + self.tool_shape.add(el, color='blue', update=False) + except TypeError: + self.tool_shape.add(geo.geo, color='blue', update=False) + + self.tool_shape.redraw() + + # for el in elements: + # self.axes.draw_artist(el) #self.canvas.canvas.blit(self.axes.bbox) # Pointer (snapped) - elements = self.axes.plot(x, y, 'bo', animated=True) - for el in elements: - self.axes.draw_artist(el) + # elements = self.axes.plot(x, y, 'bo', animated=True) + # for el in elements: + # self.axes.draw_artist(el) - self.canvas.canvas.blit(self.axes.bbox) + # self.canvas.canvas.blit(self.axes.bbox) def on_canvas_key(self, event): """ @@ -1132,6 +1157,8 @@ class FlatCAMDraw(QtCore.QObject): linewidth=linewidth, animated=animated) + self.shapes.add(geometry, color='red') + if type(geometry) == LineString or type(geometry) == LinearRing: x, y = geometry.coords.xy element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) @@ -1154,6 +1181,7 @@ class FlatCAMDraw(QtCore.QObject): """ self.app.log.debug("plot_all()") self.axes.cla() + self.shapes.clear(update=True) for shape in self.storage.get_objects(): if shape.geo is None: # TODO: This shouldn't have happened @@ -1169,6 +1197,7 @@ class FlatCAMDraw(QtCore.QObject): self.plot_shape(geometry=shape.geo, linespec='k--', linewidth=1) continue + self.shapes.redraw() self.canvas.auto_adjust_axes() def on_shape_complete(self): @@ -1179,6 +1208,7 @@ class FlatCAMDraw(QtCore.QObject): # Remove any utility shapes self.delete_utility_geometry() + self.tool_shape.clear(update=True) # Replot and reset tool. self.replot() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 9f3841a..e55e545 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -43,7 +43,8 @@ class FlatCAMObj(QtCore.QObject): self.axes = None # Matplotlib axes self.kind = None # Override with proper name - self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene) + # self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene) + self.shapes = self.app.plotcanvas.vispy_canvas.shapes self.muted_ui = False @@ -259,7 +260,7 @@ class FlatCAMObj(QtCore.QObject): # Clear axes or we will plot on top of them. self.axes.cla() # TODO: Thread safe? - self.clear_shapes() + # self.clear_shapes() return True def clear_shapes(self, update=False): @@ -1161,7 +1162,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): self.plot2(self.axes, tooldia=self.options["tooldia"], shapes=self.shapes) - self.shapes.redraw() + # self.shapes.redraw() # self.app.plotcanvas.auto_adjust_axes() def convert_units(self, units): diff --git a/PlotCanvas.py b/PlotCanvas.py index e14d536..cb7992d 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -211,6 +211,10 @@ class PlotCanvas(QtCore.QObject): """ self.key = None + def vis_connect(self, event_name, callback): + + return getattr(self.vispy_canvas.events, event_name).connect(callback) + def mpl_connect(self, event_name, callback): """ Attach an event handler to the canvas through the Matplotlib interface. diff --git a/VisPyCanvas.py b/VisPyCanvas.py index ec864f1..600bc76 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -2,6 +2,8 @@ import numpy as np from PyQt4.QtGui import QPalette import vispy.scene as scene from vispy.geometry import Rect +from vispy.app.canvas import MouseEvent +from VisPyVisuals import ShapeCollection class VisPyCanvas(scene.SceneCanvas): @@ -44,11 +46,17 @@ class VisPyCanvas(scene.SceneCanvas): # shapes = scene.Line(parent=view.scene) # view.add(shapes) - print "config", self.context.config - + self.grid = grid1 self.view = view + self.shapes = ShapeCollection(parent=view.scene) self.freeze() + self.measure_fps() + + def translate_coords(self, pos): + tr = self.grid.get_transform('canvas', 'visual') + return tr.map(pos) + def fit_view(self): rect = Rect() rect.left, rect.right = self.view.get_scene_bounds(dim=0) diff --git a/VisPyVisuals.py b/VisPyVisuals.py index ec1c803..35149dc 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -5,18 +5,26 @@ from vispy.geometry.triangulation import Triangulation from vispy.color import Color from shapely.geometry import Polygon, LineString, LinearRing import numpy as np -from shapely.ops import triangulate -import Polygon as gpc +try: + from shapely.ops import triangulate + import Polygon as gpc +except: + pass class ShapeCollectionVisual(CompoundVisual): - def __init__(self, line_width=1, **kwargs): + + total_segments = 0 + total_tris = 0 + + def __init__(self, line_width=1, triangulation='gpc', **kwargs): self.data = {} self.last_key = -1 self._mesh = MeshVisual() self._line = LineVisual(antialias=True) self._line_width = line_width + self._triangulation = triangulation CompoundVisual.__init__(self, [self._mesh, self._line], **kwargs) self._mesh.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) @@ -68,6 +76,8 @@ class ShapeCollectionVisual(CompoundVisual): # Creating arrays for mesh and line from all shapes for shape, color, face_color in self.data.values(): + if shape is None or shape.is_empty: + continue simple = shape.simplify(0.01) # Simplified shape pts = np.empty((0, 2)) # Shape line points @@ -85,44 +95,51 @@ class ShapeCollectionVisual(CompoundVisual): elif type(shape) == Polygon: # Prepare polygon faces if face_color is not None: - # Concatenated arrays of external & internal line rings - # vertices = self._open_ring(np.array(simple.exterior)) - # edges = self._generate_edges(len(vertices)) - # - # print "poly exterior pts:", len(vertices) - # - # for ints in simple.interiors: - # v = self._open_ring(np.array(ints)) - # edges = np.append(edges, self._generate_edges(len(v)) + len(vertices), 0) - # vertices = np.append(vertices, v, 0) - # - # print "poly interior pts:", len(v) - # - # tri = Triangulation(vertices, edges) - # tri.triangulate() - # tri_pts, tri_tris = tri.pts, tri.tris + + if self._triangulation == 'vispy': + # VisPy triangulation + # Concatenated arrays of external & internal line rings + vertices = self._open_ring(np.array(simple.exterior)) + edges = self._generate_edges(len(vertices)) + + print "poly exterior pts:", len(vertices) + + for ints in simple.interiors: + v = self._open_ring(np.array(ints)) + edges = np.append(edges, self._generate_edges(len(v)) + len(vertices), 0) + vertices = np.append(vertices, v, 0) + + print "poly interior pts:", len(v) + + tri = Triangulation(vertices, edges) + tri.triangulate() + tri_pts, tri_tris = tri.pts, tri.tris + elif self._triangulation == 'gpc': + + # GPC triangulation + p = gpc.Polygon(np.asarray(simple.exterior)) + + for ints in simple.interiors: + q = gpc.Polygon(np.asarray(ints)) + p -= q + + tri_pts = np.empty((0, 2)) + tri_tris = np.empty((0, 3)) + + for strip in p.triStrip(): + # Generate tris indexes for triangle strips + a = np.repeat(np.arange(0, len(strip) - 2), 3).reshape((-1, 3)) + a[:, 1] += 1 + a[:, 2] += 2 + + # Append vertices & tris + tri_tris = np.append(tri_tris, a + len(tri_pts), 0) + tri_pts = np.append(tri_pts, np.asarray(strip), 0) # Shapely triangulation # tri_pts = np.array(map(lambda x: np.array(x.exterior)[:-1], triangulate(shape))).reshape(-1, 2) # tri_tris = np.arange(0, len(tri_pts), dtype=np.uint32).reshape((-1, 3)) - p = gpc.Polygon(np.asarray(simple.exterior)) - - for ints in simple.interiors: - q = gpc.Polygon(np.asarray(ints)) - p -= q - - tri_pts = np.empty((0, 2)) - tri_tris = np.empty((0, 3)) - - for strip in p.triStrip(): - a = np.repeat(np.arange(0, len(strip) - 2), 3).reshape((-1, 3)) - a[:, 1] += 1 - a[:, 2] += 2 - - tri_tris = np.append(tri_tris, a + len(tri_pts), 0) - tri_pts = np.append(tri_pts, np.asarray(strip), 0) - # Prepare polygon edges if color is not None: pts = self._linearring_to_segments(np.asarray(simple.exterior)) @@ -149,6 +166,9 @@ class ShapeCollectionVisual(CompoundVisual): if len(mesh_vertices) > 0: set_state(polygon_offset_fill=False) self._mesh.set_data(mesh_vertices, mesh_tris.astype(np.uint32), face_colors=mesh_colors) + + self.total_tris += len(mesh_tris) + else: self._mesh.set_data() @@ -156,6 +176,9 @@ class ShapeCollectionVisual(CompoundVisual): if len(line_pts) > 0: set_state(blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self._line.set_data(line_pts, line_colors, self._line_width, 'segments') + + self.total_segments += len(line_pts) / 2 + else: self._line._bounds = None self._line._pos = None @@ -173,7 +196,6 @@ class ShapeCollectionVisual(CompoundVisual): return edges def _linearring_to_segments(self, arr): - # Close linear ring if np.any(arr[0] != arr[-1]): arr = np.concatenate([arr, arr[:1]], axis=0) @@ -188,6 +210,7 @@ class ShapeCollectionVisual(CompoundVisual): def redraw(self): self._update() + print "total:", self.total_segments, self.total_tris ShapeCollection = create_visual_node(ShapeCollectionVisual) From 539faca5e202a94530f76c996d802cc9260e34e4 Mon Sep 17 00:00:00 2001 From: Denvi Date: Wed, 13 Jul 2016 22:07:50 +0500 Subject: [PATCH 09/26] ShapeCollection _update on python lists --- FlatCAMObj.py | 50 ++++++++++++++++---------------- VisPyVisuals.py | 77 ++++++++++++++++++++++++++++++------------------- 2 files changed, 72 insertions(+), 55 deletions(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index e55e545..737fd65 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -119,25 +119,25 @@ class FlatCAMObj(QtCore.QObject): :rtype: None """ - if self.axes is None: - FlatCAMApp.App.log.debug("setup_axes(): New axes") - self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9], - label=self.options["name"]) - elif self.axes not in figure.axes: - FlatCAMApp.App.log.debug("setup_axes(): Clearing and attaching axes") - self.axes.cla() - figure.add_axes(self.axes) - else: - FlatCAMApp.App.log.debug("setup_axes(): Clearing Axes") - self.axes.cla() - - # Remove all decoration. The app's axes will have - # the ticks and grid. - self.axes.set_frame_on(False) # No frame - self.axes.set_xticks([]) # No tick - self.axes.set_yticks([]) # No ticks - self.axes.patch.set_visible(False) # No background - self.axes.set_aspect(1) + # if self.axes is None: + # FlatCAMApp.App.log.debug("setup_axes(): New axes") + # self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9], + # label=self.options["name"]) + # elif self.axes not in figure.axes: + # FlatCAMApp.App.log.debug("setup_axes(): Clearing and attaching axes") + # self.axes.cla() + # figure.add_axes(self.axes) + # else: + # FlatCAMApp.App.log.debug("setup_axes(): Clearing Axes") + # self.axes.cla() + # + # # Remove all decoration. The app's axes will have + # # the ticks and grid. + # self.axes.set_frame_on(False) # No frame + # self.axes.set_xticks([]) # No tick + # self.axes.set_yticks([]) # No ticks + # self.axes.patch.set_visible(False) # No background + # self.axes.set_aspect(1) def to_form(self): """ @@ -249,17 +249,17 @@ class FlatCAMObj(QtCore.QObject): FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()") # Axes must exist and be attached to canvas. - if self.axes is None or self.axes not in self.app.plotcanvas.figure.axes: - self.axes = self.app.plotcanvas.new_axes(self.options['name']) + # if self.axes is None or self.axes not in self.app.plotcanvas.figure.axes: + # self.axes = self.app.plotcanvas.new_axes(self.options['name']) if not self.options["plot"]: - self.axes.cla() - self.app.plotcanvas.auto_adjust_axes() + # self.axes.cla() + # self.app.plotcanvas.auto_adjust_axes() self.clear_shapes(update=True) return False # Clear axes or we will plot on top of them. - self.axes.cla() # TODO: Thread safe? + # self.axes.cla() # TODO: Thread safe? # self.clear_shapes() return True @@ -1562,6 +1562,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): # FlatCAMApp.App.log.warning("Did not plot:", str(type(geo))) self.plot_element(self.solid_geometry) - self.shapes.redraw() + # self.shapes.redraw() # self.app.plotcanvas.auto_adjust_axes() diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 35149dc..de22f59 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -67,12 +67,17 @@ class ShapeCollectionVisual(CompoundVisual): self._update() def _update(self): - mesh_vertices = np.empty((0, 2)) # Vertices for mesh - mesh_tris = np.empty((0, 3)) # Faces for mesh - mesh_colors = np.empty((0, 4)) # Face colors + # mesh_vertices = np.empty((0, 2)) # Vertices for mesh + mesh_vertices = [] + # mesh_tris = np.empty((0, 3)) # Faces for mesh + mesh_tris = [] + # mesh_colors = np.empty((0, 4)) # Face colors + mesh_colors = [] - line_pts = np.empty((0, 2)) # Vertices for line - line_colors = np.empty((0, 4)) # Line color + # line_pts = np.empty((0, 2)) # Vertices for line + line_pts = [] + # line_colors = np.empty((0, 4)) # Line color + line_colors = [] # Creating arrays for mesh and line from all shapes for shape, color, face_color in self.data.values(): @@ -80,32 +85,34 @@ class ShapeCollectionVisual(CompoundVisual): continue simple = shape.simplify(0.01) # Simplified shape - pts = np.empty((0, 2)) # Shape line points - tri_pts = np.empty((0, 2)) # Mesh vertices - tri_tris = np.empty((0, 3)) # Mesh faces + # pts = np.empty((0, 2)) # Shape line points + pts = [] + # tri_pts = np.empty((0, 2)) # Mesh vertices + tri_pts = [] + # tri_tris = np.empty((0, 3)) # Mesh faces + tri_tris = [] if type(shape) == LineString: # Prepare lines - pts = self._linestring_to_segments(np.asarray(simple)) + pts = self._linestring_to_segments(np.asarray(simple)).tolist() elif type(shape) == LinearRing: # Prepare lines - pts = self._linearring_to_segments(np.asarray(simple)) + pts = self._linearring_to_segments(np.asarray(simple)).tolist() elif type(shape) == Polygon: # Prepare polygon faces if face_color is not None: - if self._triangulation == 'vispy': # VisPy triangulation # Concatenated arrays of external & internal line rings - vertices = self._open_ring(np.array(simple.exterior)) + vertices = self._open_ring(np.asarray(simple.exterior)) edges = self._generate_edges(len(vertices)) print "poly exterior pts:", len(vertices) for ints in simple.interiors: - v = self._open_ring(np.array(ints)) + v = self._open_ring(np.asarray(ints)) edges = np.append(edges, self._generate_edges(len(v)) + len(vertices), 0) vertices = np.append(vertices, v, 0) @@ -123,18 +130,16 @@ class ShapeCollectionVisual(CompoundVisual): q = gpc.Polygon(np.asarray(ints)) p -= q - tri_pts = np.empty((0, 2)) - tri_tris = np.empty((0, 3)) - for strip in p.triStrip(): # Generate tris indexes for triangle strips - a = np.repeat(np.arange(0, len(strip) - 2), 3).reshape((-1, 3)) - a[:, 1] += 1 - a[:, 2] += 2 + a = (np.repeat(np.arange(0, len(strip) - 2), 3).reshape((-1, 3)) + (0, 1, 2)).tolist() + # a = [[x + y for x in range(0, 3)] for y in range(0, len(strip) - 2)] # Append vertices & tris - tri_tris = np.append(tri_tris, a + len(tri_pts), 0) - tri_pts = np.append(tri_pts, np.asarray(strip), 0) + # tri_tris = np.append(tri_tris, a + len(tri_pts), 0) + tri_tris += [[x + len(tri_pts) for x in y] for y in a] + # tri_pts = np.append(tri_pts, np.asarray(strip), 0) + tri_pts += strip # Shapely triangulation # tri_pts = np.array(map(lambda x: np.array(x.exterior)[:-1], triangulate(shape))).reshape(-1, 2) @@ -142,15 +147,20 @@ class ShapeCollectionVisual(CompoundVisual): # Prepare polygon edges if color is not None: - pts = self._linearring_to_segments(np.asarray(simple.exterior)) + pts = self._linearring_to_segments(np.asarray(simple.exterior)).tolist() + # pts = self._linearring_to_segments(np.asarray(simple.exterior)) for ints in simple.interiors: - pts = np.append(pts, self._linearring_to_segments(np.asarray(ints)), 0) + # pts = np.append(pts, self._linearring_to_segments(np.asarray(ints)), 0) + pts += self._linearring_to_segments(np.asarray(ints)).tolist() # Appending data for mesh if len(tri_pts) > 0 and len(tri_tris) > 0: - mesh_tris = np.append(mesh_tris, tri_tris + len(mesh_vertices), 0) - mesh_vertices = np.append(mesh_vertices, tri_pts, 0) - mesh_colors = np.append(mesh_colors, np.full((len(tri_tris), 4), Color(face_color).rgba), 0) + # mesh_tris = np.append(mesh_tris, tri_tris + len(mesh_vertices), 0) + mesh_tris += [[x + len(mesh_vertices) for x in y] for y in tri_tris] + # mesh_vertices = np.append(mesh_vertices, tri_pts, 0) + mesh_vertices += tri_pts + # mesh_colors = np.append(mesh_colors, np.full((len(tri_tris), 4), Color(face_color).rgba), 0) + mesh_colors += [Color(face_color).rgba] * len(tri_tris) # Random face colors # rc = np.random.rand(len(tri_tris), 4) @@ -159,13 +169,20 @@ class ShapeCollectionVisual(CompoundVisual): # Appending data for line if len(pts) > 0: - line_pts = np.append(line_pts, pts, 0) - line_colors = np.append(line_colors, np.full((len(pts), 4), Color(color).rgba), 0) + # line_pts = np.append(line_pts, pts, 0) + try: + line_pts += pts + except ValueError as e: + print "exception", pts + # line_colors = np.append(line_colors, np.full((len(pts), 4), Color(color).rgba), 0) + line_colors += [Color(color).rgba] * len(pts) # Updating mesh if len(mesh_vertices) > 0: set_state(polygon_offset_fill=False) - self._mesh.set_data(mesh_vertices, mesh_tris.astype(np.uint32), face_colors=mesh_colors) + # self._mesh.set_data(mesh_vertices, mesh_tris.astype(np.uint32), face_colors=mesh_colors) + self._mesh.set_data(np.asarray(mesh_vertices), np.asarray(mesh_tris, dtype=np.uint32), + face_colors=np.asarray(mesh_colors)) self.total_tris += len(mesh_tris) @@ -175,7 +192,7 @@ class ShapeCollectionVisual(CompoundVisual): # Updating line if len(line_pts) > 0: set_state(blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) - self._line.set_data(line_pts, line_colors, self._line_width, 'segments') + self._line.set_data(np.asarray(line_pts), np.asarray(line_colors), self._line_width, 'segments') self.total_segments += len(line_pts) / 2 From 63e59420409332b0547f114b7417b85d6689bef4 Mon Sep 17 00:00:00 2001 From: Denvi Date: Wed, 13 Jul 2016 23:05:07 +0500 Subject: [PATCH 10/26] Geometry object color changed --- FlatCAMObj.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 737fd65..77a199b 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1494,7 +1494,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): except TypeError: # Element is not iterable... - self.shapes.add(element, color='red') + self.shapes.add(element, color='#5E6CFFFF') # if type(element) == Polygon: # x, y = element.exterior.coords.xy From 87a98e716004a476d12c6c1734fee5fac23d4472 Mon Sep 17 00:00:00 2001 From: HDR Date: Thu, 14 Jul 2016 09:45:33 +0600 Subject: [PATCH 11/26] Vispy triangulation updated --- VisPyVisuals.py | 51 +++++++++++++------------------------------------ 1 file changed, 13 insertions(+), 38 deletions(-) diff --git a/VisPyVisuals.py b/VisPyVisuals.py index de22f59..247c7f6 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -17,7 +17,7 @@ class ShapeCollectionVisual(CompoundVisual): total_segments = 0 total_tris = 0 - def __init__(self, line_width=1, triangulation='gpc', **kwargs): + def __init__(self, line_width=1, triangulation='vispy', **kwargs): self.data = {} self.last_key = -1 @@ -67,17 +67,12 @@ class ShapeCollectionVisual(CompoundVisual): self._update() def _update(self): - # mesh_vertices = np.empty((0, 2)) # Vertices for mesh - mesh_vertices = [] - # mesh_tris = np.empty((0, 3)) # Faces for mesh - mesh_tris = [] - # mesh_colors = np.empty((0, 4)) # Face colors - mesh_colors = [] + mesh_vertices = [] # Vertices for mesh + mesh_tris = [] # Faces for mesh + mesh_colors = [] # Face colors - # line_pts = np.empty((0, 2)) # Vertices for line - line_pts = [] - # line_colors = np.empty((0, 4)) # Line color - line_colors = [] + line_pts = [] # Vertices for line + line_colors = [] # Line color # Creating arrays for mesh and line from all shapes for shape, color, face_color in self.data.values(): @@ -85,12 +80,9 @@ class ShapeCollectionVisual(CompoundVisual): continue simple = shape.simplify(0.01) # Simplified shape - # pts = np.empty((0, 2)) # Shape line points - pts = [] - # tri_pts = np.empty((0, 2)) # Mesh vertices - tri_pts = [] - # tri_tris = np.empty((0, 3)) # Mesh faces - tri_tris = [] + pts = [] # Shape line points + tri_pts = [] # Mesh vertices + tri_tris = [] # Mesh faces if type(shape) == LineString: # Prepare lines @@ -120,7 +112,8 @@ class ShapeCollectionVisual(CompoundVisual): tri = Triangulation(vertices, edges) tri.triangulate() - tri_pts, tri_tris = tri.pts, tri.tris + tri_pts, tri_tris = tri.pts.tolist(), tri.tris.tolist() + elif self._triangulation == 'gpc': # GPC triangulation @@ -132,34 +125,22 @@ class ShapeCollectionVisual(CompoundVisual): for strip in p.triStrip(): # Generate tris indexes for triangle strips - a = (np.repeat(np.arange(0, len(strip) - 2), 3).reshape((-1, 3)) + (0, 1, 2)).tolist() - # a = [[x + y for x in range(0, 3)] for y in range(0, len(strip) - 2)] + a = [[x + y for x in range(0, 3)] for y in range(0, len(strip) - 2)] # Append vertices & tris - # tri_tris = np.append(tri_tris, a + len(tri_pts), 0) tri_tris += [[x + len(tri_pts) for x in y] for y in a] - # tri_pts = np.append(tri_pts, np.asarray(strip), 0) tri_pts += strip - # Shapely triangulation - # tri_pts = np.array(map(lambda x: np.array(x.exterior)[:-1], triangulate(shape))).reshape(-1, 2) - # tri_tris = np.arange(0, len(tri_pts), dtype=np.uint32).reshape((-1, 3)) - # Prepare polygon edges if color is not None: pts = self._linearring_to_segments(np.asarray(simple.exterior)).tolist() - # pts = self._linearring_to_segments(np.asarray(simple.exterior)) for ints in simple.interiors: - # pts = np.append(pts, self._linearring_to_segments(np.asarray(ints)), 0) pts += self._linearring_to_segments(np.asarray(ints)).tolist() # Appending data for mesh if len(tri_pts) > 0 and len(tri_tris) > 0: - # mesh_tris = np.append(mesh_tris, tri_tris + len(mesh_vertices), 0) mesh_tris += [[x + len(mesh_vertices) for x in y] for y in tri_tris] - # mesh_vertices = np.append(mesh_vertices, tri_pts, 0) mesh_vertices += tri_pts - # mesh_colors = np.append(mesh_colors, np.full((len(tri_tris), 4), Color(face_color).rgba), 0) mesh_colors += [Color(face_color).rgba] * len(tri_tris) # Random face colors @@ -169,18 +150,12 @@ class ShapeCollectionVisual(CompoundVisual): # Appending data for line if len(pts) > 0: - # line_pts = np.append(line_pts, pts, 0) - try: - line_pts += pts - except ValueError as e: - print "exception", pts - # line_colors = np.append(line_colors, np.full((len(pts), 4), Color(color).rgba), 0) + line_pts += pts line_colors += [Color(color).rgba] * len(pts) # Updating mesh if len(mesh_vertices) > 0: set_state(polygon_offset_fill=False) - # self._mesh.set_data(mesh_vertices, mesh_tris.astype(np.uint32), face_colors=mesh_colors) self._mesh.set_data(np.asarray(mesh_vertices), np.asarray(mesh_tris, dtype=np.uint32), face_colors=np.asarray(mesh_colors)) From 86f746bf7a6e0a3fb839fe746eaba76e53cbffdd Mon Sep 17 00:00:00 2001 From: HDR Date: Thu, 14 Jul 2016 12:36:09 +0600 Subject: [PATCH 12/26] Shape groups added --- FlatCAMApp.py | 3 +- FlatCAMObj.py | 56 ++++++++--------- ObjectCollection.py | 4 +- PlotCanvas.py | 4 ++ VisPyCanvas.py | 2 +- VisPyVisuals.py | 144 +++++++++++++++++++++++++++++++------------- camlib.py | 7 ++- 7 files changed, 144 insertions(+), 76 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index a372c33..3c2b61b 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1442,7 +1442,7 @@ class App(QtCore.QObject): return # Remove plot - self.plotcanvas.figure.delaxes(self.collection.get_active().axes) + # self.plotcanvas.figure.delaxes(self.collection.get_active().axes) self.plotcanvas.auto_adjust_axes() # Clear form @@ -2225,7 +2225,6 @@ class App(QtCore.QObject): App.log.debug(obj['kind'] + ": " + obj['options']['name']) self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True) - self.plotcanvas.vispy_canvas.shapes.redraw() # self.plot_all() self.inform.emit("Project loaded from: " + filename) App.log.debug("Project loaded") diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 77a199b..65b005d 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -44,7 +44,7 @@ class FlatCAMObj(QtCore.QObject): self.kind = None # Override with proper name # self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene) - self.shapes = self.app.plotcanvas.vispy_canvas.shapes + self.shapes = self.app.plotcanvas.new_shape_group() self.muted_ui = False @@ -252,20 +252,17 @@ class FlatCAMObj(QtCore.QObject): # if self.axes is None or self.axes not in self.app.plotcanvas.figure.axes: # self.axes = self.app.plotcanvas.new_axes(self.options['name']) - if not self.options["plot"]: - # self.axes.cla() - # self.app.plotcanvas.auto_adjust_axes() - self.clear_shapes(update=True) - return False + # if not self.options["plot"]: + # # self.axes.cla() + # # self.app.plotcanvas.auto_adjust_axes() + # self.clear_shapes(update=True) + # return False # Clear axes or we will plot on top of them. # self.axes.cla() # TODO: Thread safe? - # self.clear_shapes() + self.shapes.clear() return True - def clear_shapes(self, update=False): - self.shapes.clear(update=update) - def serialize(self): """ Returns a representation of the object as a dictionary so @@ -556,7 +553,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if self.muted_ui: return self.read_form_item('plot') - self.plot() + self.shapes.visible = self.options['plot'] + # self.plot() def on_solid_cb_click(self, *args): if self.muted_ui: @@ -606,19 +604,20 @@ class FlatCAMGerber(FlatCAMObj, Gerber): except TypeError: geometry = [geometry] - if self.options["multicolored"]: - linespec = '-' - else: - linespec = 'k-' + # if self.options["multicolored"]: + # linespec = '-' + # else: + # linespec = 'k-' if self.options["solid"]: for poly in geometry: if self.options["multicolored"]: + # TODO: Rework random color generation color = (np.random.sample(1), np.random.sample(1), np.random.sample(1), 0.75) else: color = '#BBF268BF' - self.shapes.add(poly, color='#006E20BF', face_color=color) + self.shapes.add(poly, color='#006E20BF', face_color=color, visible=self.options['plot']) # # TODO: Too many things hardcoded. # try: @@ -638,7 +637,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): else: color = 'black' - self.shapes.add(poly, color=color) + self.shapes.add(poly, color=color, visible=self.options['plot']) # x, y = poly.exterior.xy # self.axes.plot(x, y, linespec) @@ -951,7 +950,8 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): if self.muted_ui: return self.read_form_item('plot') - self.plot() + self.shapes.visible = self.options['plot'] + # self.plot() def on_solid_cb_click(self, *args): if self.muted_ui: @@ -981,7 +981,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # Plot excellon (All polygons?) if self.options["solid"]: for geo in self.solid_geometry: - self.shapes.add(geo, color='#750000BF', face_color='#C40000BF') + self.shapes.add(geo, color='#750000BF', face_color='#C40000BF', visible=self.options['plot']) # patch = PolygonPatch(geo, # facecolor="#C40000", @@ -991,12 +991,12 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): # self.axes.add_patch(patch) else: for geo in self.solid_geometry: - self.shapes.add(geo.exterior, color='red') + self.shapes.add(geo.exterior, color='red', visible=self.options['plot']) # x, y = geo.exterior.coords.xy # self.axes.plot(x, y, 'r-') for ints in geo.interiors: - self.shapes.add(ints, color='green') + self.shapes.add(ints, color='green', visible=self.options['plot']) # x, y = ints.coords.xy # self.axes.plot(x, y, 'g-') @@ -1151,7 +1151,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if self.muted_ui: return self.read_form_item('plot') - self.plot() + self.shapes.visible = self.options['plot'] + # self.plot() def plot(self): @@ -1160,9 +1161,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if not FlatCAMObj.plot(self): return - self.plot2(self.axes, tooldia=self.options["tooldia"], shapes=self.shapes) + self.plot2(self.axes, tooldia=self.options["tooldia"], shapes=self.shapes, visible=self.options['plot']) - # self.shapes.redraw() + self.shapes.redraw() # self.app.plotcanvas.auto_adjust_axes() def convert_units(self, units): @@ -1433,7 +1434,8 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): if self.muted_ui: return self.read_form_item('plot') - self.plot() + self.shapes.visible = self.options['plot'] + # self.plot() def scale(self, factor): """ @@ -1494,7 +1496,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): except TypeError: # Element is not iterable... - self.shapes.add(element, color='#5E6CFFFF') + self.shapes.add(element, color='#5E6CFFFF', visible=self.options['plot']) # if type(element) == Polygon: # x, y = element.exterior.coords.xy @@ -1562,6 +1564,6 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): # FlatCAMApp.App.log.warning("Did not plot:", str(type(geo))) self.plot_element(self.solid_geometry) - # self.shapes.redraw() + self.shapes.redraw() # self.app.plotcanvas.auto_adjust_axes() diff --git a/ObjectCollection.py b/ObjectCollection.py index d47c979..33d9557 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -214,7 +214,7 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginRemoveRows(QtCore.QModelIndex(), row, row) - self.object_list[row].shapes.parent = None + self.object_list[row].shapes.clear(True) self.object_list.pop(row) self.endRemoveRows() @@ -297,7 +297,7 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginResetModel() for obj in self.object_list: - obj.shapes.parent = None + obj.shapes.clear(True) self.object_list = [] self.checked_indexes = [] diff --git a/PlotCanvas.py b/PlotCanvas.py index cb7992d..281c9ed 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -18,6 +18,7 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg import FlatCAMApp import logging from VisPyCanvas import VisPyCanvas +from VisPyVisuals import ShapeGroup log = logging.getLogger('base') @@ -413,6 +414,9 @@ class PlotCanvas(QtCore.QObject): return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name) + def new_shape_group(self): + return ShapeGroup(self.vispy_canvas.shape_collection) + def on_scroll(self, event): """ Scroll event handler. diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 600bc76..bbc7a52 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -48,7 +48,7 @@ class VisPyCanvas(scene.SceneCanvas): self.grid = grid1 self.view = view - self.shapes = ShapeCollection(parent=view.scene) + self.shape_collection = ShapeCollection(parent=view.scene) self.freeze() self.measure_fps() diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 247c7f6..a9c1681 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -5,6 +5,7 @@ from vispy.geometry.triangulation import Triangulation from vispy.color import Color from shapely.geometry import Polygon, LineString, LinearRing import numpy as np +import time try: from shapely.ops import triangulate @@ -12,6 +13,43 @@ try: except: pass + +class ShapeGroup(object): + def __init__(self, collection): + self._collection = collection + self._indexes = [] + self._visible = True + + def add(self, shape, color=None, face_color=None, visible=None, update=False): + self._indexes.append(self._collection.add(shape, color, face_color, visible, update)) + + def clear(self, update=False): + for i in self._indexes: + self._collection.remove(i, False) + + self._indexes = [] + + if update: + self._collection.redraw() + + def redraw(self): + t0 = time.time() + self._collection.redraw() + print "group redraw time:", time.time() - t0 + + @property + def visible(self): + return self._visible + + @visible.setter + def visible(self, value): + self._visible = value + for i in self._indexes: + self._collection.data[i]['visible'] = value + + self._collection.redraw() + + class ShapeCollectionVisual(CompoundVisual): total_segments = 0 @@ -34,7 +72,7 @@ class ShapeCollectionVisual(CompoundVisual): def __del__(self): print "ShapeCollection destructed" - def add(self, shape, color=None, face_color=None, update=False): + def add(self, shape, color=None, face_color=None, visible=True, update=False): """Adds geometry object to collection Args: @@ -51,65 +89,53 @@ class ShapeCollectionVisual(CompoundVisual): """ self.last_key += 1 - self.data[self.last_key] = shape, color, face_color + + self.data[self.last_key] = {'geometry': shape, 'color': color, 'face_color': face_color, 'visible': visible} + self.update_shape_buffers(self.last_key) + if update: self._update() + return self.last_key - def remove(self, key, update=False): - del self.data[key] - if update: - self._update() + def update_shape_buffers(self, key): + mesh_vertices = [] # Vertices for mesh + mesh_tris = [] # Faces for mesh + mesh_colors = [] # Face colors + line_pts = [] # Vertices for line + line_colors = [] # Line color - def clear(self, update=False): - self.data = {} - if update: - self._update() + geo, color, face_color = self.data[key]['geometry'], self.data[key]['color'], self.data[key]['face_color'] - def _update(self): - mesh_vertices = [] # Vertices for mesh - mesh_tris = [] # Faces for mesh - mesh_colors = [] # Face colors + if geo is not None and not geo.is_empty: + simple = geo.simplify(0.01) # Simplified shape + pts = [] # Shape line points + tri_pts = [] # Mesh vertices + tri_tris = [] # Mesh faces - line_pts = [] # Vertices for line - line_colors = [] # Line color - - # Creating arrays for mesh and line from all shapes - for shape, color, face_color in self.data.values(): - if shape is None or shape.is_empty: - continue - - simple = shape.simplify(0.01) # Simplified shape - pts = [] # Shape line points - tri_pts = [] # Mesh vertices - tri_tris = [] # Mesh faces - - if type(shape) == LineString: + if type(geo) == LineString: # Prepare lines pts = self._linestring_to_segments(np.asarray(simple)).tolist() - elif type(shape) == LinearRing: + elif type(geo) == LinearRing: # Prepare lines pts = self._linearring_to_segments(np.asarray(simple)).tolist() - elif type(shape) == Polygon: + elif type(geo) == Polygon: # Prepare polygon faces if face_color is not None: + if self._triangulation == 'vispy': # VisPy triangulation # Concatenated arrays of external & internal line rings vertices = self._open_ring(np.asarray(simple.exterior)) edges = self._generate_edges(len(vertices)) - print "poly exterior pts:", len(vertices) - for ints in simple.interiors: v = self._open_ring(np.asarray(ints)) edges = np.append(edges, self._generate_edges(len(v)) + len(vertices), 0) vertices = np.append(vertices, v, 0) - print "poly interior pts:", len(v) - tri = Triangulation(vertices, edges) tri.triangulate() tri_pts, tri_tris = tri.pts.tolist(), tri.tris.tolist() @@ -139,20 +165,55 @@ class ShapeCollectionVisual(CompoundVisual): # Appending data for mesh if len(tri_pts) > 0 and len(tri_tris) > 0: - mesh_tris += [[x + len(mesh_vertices) for x in y] for y in tri_tris] + mesh_tris += tri_tris mesh_vertices += tri_pts mesh_colors += [Color(face_color).rgba] * len(tri_tris) - # Random face colors - # rc = np.random.rand(len(tri_tris), 4) - # rc[:, 3] = 1.0 - # mesh_colors = np.append(mesh_colors, rc, 0) - # Appending data for line if len(pts) > 0: line_pts += pts line_colors += [Color(color).rgba] * len(pts) + # Store buffers + self.data[key]['line_pts'] = line_pts + self.data[key]['line_colors'] = line_colors + self.data[key]['mesh_vertices'] = mesh_vertices + self.data[key]['mesh_tris'] = mesh_tris + self.data[key]['mesh_colors'] = mesh_colors + + def remove(self, key, update=False): + del self.data[key] + if update: + self._update() + + def clear(self, update=False): + self.data = {} + if update: + self._update() + + def _update(self): + + print "updating shape collection" + t0 = time.time() + + mesh_vertices = [] # Vertices for mesh + mesh_tris = [] # Faces for mesh + mesh_colors = [] # Face colors + line_pts = [] # Vertices for line + line_colors = [] # Line color + + # Merge shapes buffers + for data in self.data.values(): + if data['visible']: + try: + line_pts += data['line_pts'] + line_colors += data['line_colors'] + mesh_tris += [[x + len(mesh_vertices) for x in y] for y in data['mesh_tris']] + mesh_vertices += data['mesh_vertices'] + mesh_colors += data['mesh_colors'] + except Exception as e: + print "Data error", e + # Updating mesh if len(mesh_vertices) > 0: set_state(polygon_offset_fill=False) @@ -166,7 +227,6 @@ class ShapeCollectionVisual(CompoundVisual): # Updating line if len(line_pts) > 0: - set_state(blend=True, blend_func=('src_alpha', 'one_minus_src_alpha')) self._line.set_data(np.asarray(line_pts), np.asarray(line_colors), self._line_width, 'segments') self.total_segments += len(line_pts) / 2 @@ -177,6 +237,8 @@ class ShapeCollectionVisual(CompoundVisual): self._line._changed['pos'] = True self._line.update() + print "update completed:", time.time() - t0 + def _open_ring(self, vertices): return vertices[:-1] if not np.any(vertices[0] != vertices[-1]) else vertices diff --git a/camlib.py b/camlib.py index c781e42..1f59172 100644 --- a/camlib.py +++ b/camlib.py @@ -3245,7 +3245,7 @@ class CNCjob(Geometry): def plot2(self, axes, tooldia=None, dpi=75, margin=0.1, color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]}, - alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, shapes=None): + alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, shapes=None, visible=False): """ Plots the G-code job onto the given axes. @@ -3271,7 +3271,7 @@ class CNCjob(Geometry): # linespec = 'k-' # x, y = geo['geom'].coords.xy # axes.plot(x, y, linespec, color=linecolor) - shapes.add(geo['geom'], color=linecolor) + shapes.add(geo['geom'], color=linecolor, visible=visible) else: for geo in self.gcode_parsed: # path_num += 1 @@ -3285,7 +3285,8 @@ class CNCjob(Geometry): # alpha=alpha[geo['kind'][0]], zorder=2) # axes.add_patch(patch) # shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) - shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) + shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0], + visible=visible) def create_geometry(self): # TODO: This takes forever. Too much data? From d0dfacc1fe36acf7f85df896c497bcc33d0c0332 Mon Sep 17 00:00:00 2001 From: HDR Date: Thu, 14 Jul 2016 14:25:28 +0600 Subject: [PATCH 13/26] Working on geometry editor --- FlatCAMDraw.py | 63 ++++++++++++++++++++++++++++++++----------------- FlatCAMObj.py | 11 +++++---- PlotCanvas.py | 14 +++++++++-- VisPyVisuals.py | 4 ++-- 4 files changed, 62 insertions(+), 30 deletions(-) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 5a340ec..ced75cb 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -19,7 +19,8 @@ from numpy.linalg import solve #from mpl_toolkits.axes_grid.anchored_artists import AnchoredDrawingArea from rtree import index as rtindex -from VisPyVisuals import ShapeCollection + +from vispy.scene.visuals import Markers class BufferSelectionTool(FlatCAMTool): """ @@ -591,7 +592,7 @@ class FlatCAMDraw(QtCore.QObject): self.app = app self.canvas = app.plotcanvas - self.axes = self.canvas.new_axes("draw") + # self.axes = self.canvas.new_axes("draw") ### Drawing Toolbar ### self.drawing_toolbar = QtGui.QToolBar() @@ -695,12 +696,14 @@ class FlatCAMDraw(QtCore.QObject): ### Data self.active_tool = None - self.storage = FlatCAMDraw.make_storage() - self.shapes = ShapeCollection() - self.tool_shape = ShapeCollection() self.utility = [] + # VisPy visuals + self.shapes = self.app.plotcanvas.new_shape_collection() + self.tool_shape = self.app.plotcanvas.new_shape_collection() + self.cursor = self.app.plotcanvas.new_cursor() + ## List of selected shapes. self.selected = [] @@ -751,8 +754,10 @@ class FlatCAMDraw(QtCore.QObject): self.snap_max_dist_entry.editingFinished.connect(lambda: entry2option("snap_max", self.snap_max_dist_entry)) def activate(self): - self.shapes.parent = self.canvas.vispy_canvas.view.scene - self.tool_shape.parent = self.canvas.vispy_canvas.view.scene + parent = self.canvas.vispy_canvas.view.scene + self.shapes.parent = parent + self.tool_shape.parent = parent + self.cursor.parent = parent def connect_canvas_event_handlers(self): ## Canvas events @@ -761,6 +766,7 @@ class FlatCAMDraw(QtCore.QObject): self.cid_canvas_key = self.canvas.mpl_connect('key_press_event', self.on_canvas_key) self.cid_canvas_key_release = self.canvas.mpl_connect('key_release_event', self.on_canvas_key_release) + # TODO: Create disconnects self.canvas.vis_connect('mouse_release', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) @@ -770,6 +776,9 @@ class FlatCAMDraw(QtCore.QObject): self.canvas.mpl_disconnect(self.cid_canvas_key) self.canvas.mpl_disconnect(self.cid_canvas_key_release) + self.canvas.vis_disconnect('mouse_release', self.on_canvas_click) + self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) + def add_shape(self, shape): """ Adds a shape to the shape storage. @@ -805,8 +814,11 @@ class FlatCAMDraw(QtCore.QObject): self.clear() self.drawing_toolbar.setDisabled(True) self.snap_toolbar.setDisabled(True) # TODO: Combine and move into tool + + # Hide vispy visuals self.shapes.parent = None self.tool_shape.parent = None + self.cursor.parent = None def delete_utility_geometry(self): #for_deletion = [shape for shape in self.shape_buffer if shape.utility] @@ -1018,6 +1030,8 @@ class FlatCAMDraw(QtCore.QObject): # for el in elements: # self.axes.draw_artist(el) + self.cursor.set_data(np.asarray([(x, y)]), symbol='s', edge_color='red', size=5) + # self.canvas.canvas.blit(self.axes.bbox) def on_canvas_key(self, event): @@ -1113,7 +1127,7 @@ class FlatCAMDraw(QtCore.QObject): self.selected = [] - def plot_shape(self, geometry=None, linespec='b-', linewidth=1, animated=False): + def plot_shape(self, geometry=None, color='black', linespec='b-', linewidth=1, animated=False): """ Plots a geometric object or list of objects without rendering. Plotted objects are returned as a list. This allows for efficient/animated rendering. @@ -1132,6 +1146,7 @@ class FlatCAMDraw(QtCore.QObject): try: for geo in geometry: plot_elements += self.plot_shape(geometry=geo, + color=color, linespec=linespec, linewidth=linewidth, animated=animated) @@ -1142,6 +1157,7 @@ class FlatCAMDraw(QtCore.QObject): ## DrawToolShape if isinstance(geometry, DrawToolShape): plot_elements += self.plot_shape(geometry=geometry.geo, + color=color, linespec=linespec, linewidth=linewidth, animated=animated) @@ -1149,25 +1165,27 @@ class FlatCAMDraw(QtCore.QObject): ## Polygon: Dscend into exterior and each interior. if type(geometry) == Polygon: plot_elements += self.plot_shape(geometry=geometry.exterior, + color=color, linespec=linespec, linewidth=linewidth, animated=animated) plot_elements += self.plot_shape(geometry=geometry.interiors, + color=color, linespec=linespec, linewidth=linewidth, animated=animated) - self.shapes.add(geometry, color='red') - if type(geometry) == LineString or type(geometry) == LinearRing: - x, y = geometry.coords.xy - element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) - plot_elements.append(element) + # x, y = geometry.coords.xy + # element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) + self.shapes.add(geometry, color=color) + # plot_elements.append(element) if type(geometry) == Point: - x, y = geometry.coords.xy - element, = self.axes.plot(x, y, 'bo', linewidth=linewidth, animated=animated) - plot_elements.append(element) + pass + # x, y = geometry.coords.xy + # element, = self.axes.plot(x, y, 'bo', linewidth=linewidth, animated=animated) + # plot_elements.append(element) return plot_elements @@ -1180,25 +1198,28 @@ class FlatCAMDraw(QtCore.QObject): :rtype: None """ self.app.log.debug("plot_all()") - self.axes.cla() + # self.axes.cla() self.shapes.clear(update=True) for shape in self.storage.get_objects(): + + print "plot shape", shape + if shape.geo is None: # TODO: This shouldn't have happened continue if shape in self.selected: - self.plot_shape(geometry=shape.geo, linespec='k-', linewidth=2) + self.plot_shape(geometry=shape.geo, color='red', linespec='k-', linewidth=2) continue - self.plot_shape(geometry=shape.geo) + self.plot_shape(geometry=shape.geo, color='blue') for shape in self.utility: self.plot_shape(geometry=shape.geo, linespec='k--', linewidth=1) continue self.shapes.redraw() - self.canvas.auto_adjust_axes() + # self.canvas.auto_adjust_axes() def on_shape_complete(self): self.app.log.debug("on_shape_complete()") @@ -1226,7 +1247,7 @@ class FlatCAMDraw(QtCore.QObject): self.selected.remove(shape) def replot(self): - self.axes = self.canvas.new_axes("draw") + # self.axes = self.canvas.new_axes("draw") self.plot_all() @staticmethod diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 65b005d..1273f39 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -612,12 +612,12 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if self.options["solid"]: for poly in geometry: if self.options["multicolored"]: - # TODO: Rework random color generation - color = (np.random.sample(1), np.random.sample(1), np.random.sample(1), 0.75) + face_color = np.random.rand(4) + face_color[3] = 1 else: - color = '#BBF268BF' + face_color = '#BBF268BF' - self.shapes.add(poly, color='#006E20BF', face_color=color, visible=self.options['plot']) + self.shapes.add(poly, color='#006E20BF', face_color=face_color, visible=self.options['plot']) # # TODO: Too many things hardcoded. # try: @@ -633,7 +633,8 @@ class FlatCAMGerber(FlatCAMObj, Gerber): else: for poly in geometry: if self.options["multicolored"]: - color = (np.random.sample(1), np.random.sample(1), np.random.sample(1), 1.0) + color = np.random.rand(4) + color[3] = 1 else: color = 'black' diff --git a/PlotCanvas.py b/PlotCanvas.py index 281c9ed..8100436 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -18,7 +18,9 @@ from matplotlib.backends.backend_agg import FigureCanvasAgg import FlatCAMApp import logging from VisPyCanvas import VisPyCanvas -from VisPyVisuals import ShapeGroup +from VisPyVisuals import ShapeGroup, ShapeCollection +from vispy.scene.visuals import Markers +import numpy as np log = logging.getLogger('base') @@ -213,9 +215,11 @@ class PlotCanvas(QtCore.QObject): self.key = None def vis_connect(self, event_name, callback): - return getattr(self.vispy_canvas.events, event_name).connect(callback) + def vis_disconnect(self, event_name, callback): + getattr(self.vispy_canvas.events, event_name).disconnect(callback) + def mpl_connect(self, event_name, callback): """ Attach an event handler to the canvas through the Matplotlib interface. @@ -417,6 +421,12 @@ class PlotCanvas(QtCore.QObject): def new_shape_group(self): return ShapeGroup(self.vispy_canvas.shape_collection) + def new_shape_collection(self): + return ShapeCollection() + + def new_cursor(self): + return Markers(pos=np.empty((0, 2))) + def on_scroll(self, event): """ Scroll event handler. diff --git a/VisPyVisuals.py b/VisPyVisuals.py index a9c1681..343efc4 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -20,7 +20,7 @@ class ShapeGroup(object): self._indexes = [] self._visible = True - def add(self, shape, color=None, face_color=None, visible=None, update=False): + def add(self, shape, color=None, face_color=None, visible=True, update=False): self._indexes.append(self._collection.add(shape, color, face_color, visible, update)) def clear(self, update=False): @@ -263,8 +263,8 @@ class ShapeCollectionVisual(CompoundVisual): return self._line._compute_bounds(axis, view) def redraw(self): + print "redrawing collection", len(self.data) self._update() - print "total:", self.total_segments, self.total_tris ShapeCollection = create_visual_node(ShapeCollectionVisual) From 184cf8296ed419425a7f4ddb5df4660466b3a14c Mon Sep 17 00:00:00 2001 From: HDR Date: Thu, 14 Jul 2016 15:04:38 +0600 Subject: [PATCH 14/26] Key press events handling added --- FlatCAMDraw.py | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index ced75cb..2f63dc7 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -501,7 +501,7 @@ class FCSelect(DrawTool): except StopIteration: return "" - if self.draw_app.key != 'control': + if self.draw_app.key != 'Control': self.draw_app.selected = [] self.draw_app.set_selected(closest_shape) @@ -761,23 +761,26 @@ class FlatCAMDraw(QtCore.QObject): def connect_canvas_event_handlers(self): ## Canvas events - self.cid_canvas_click = self.canvas.mpl_connect('button_press_event', self.on_canvas_click) - self.cid_canvas_move = self.canvas.mpl_connect('motion_notify_event', self.on_canvas_move) - self.cid_canvas_key = self.canvas.mpl_connect('key_press_event', self.on_canvas_key) - self.cid_canvas_key_release = self.canvas.mpl_connect('key_release_event', self.on_canvas_key_release) + # self.cid_canvas_click = self.canvas.mpl_connect('button_press_event', self.on_canvas_click) + # self.cid_canvas_move = self.canvas.mpl_connect('motion_notify_event', self.on_canvas_move) + # self.cid_canvas_key = self.canvas.mpl_connect('key_press_event', self.on_canvas_key) + # self.cid_canvas_key_release = self.canvas.mpl_connect('key_release_event', self.on_canvas_key_release) - # TODO: Create disconnects self.canvas.vis_connect('mouse_release', self.on_canvas_click) self.canvas.vis_connect('mouse_move', self.on_canvas_move) + self.canvas.vis_connect('key_press', self.on_canvas_key) + self.canvas.vis_connect('key_release', self.on_canvas_key_release) def disconnect_canvas_event_handlers(self): - self.canvas.mpl_disconnect(self.cid_canvas_click) - self.canvas.mpl_disconnect(self.cid_canvas_move) - self.canvas.mpl_disconnect(self.cid_canvas_key) - self.canvas.mpl_disconnect(self.cid_canvas_key_release) + # self.canvas.mpl_disconnect(self.cid_canvas_click) + # self.canvas.mpl_disconnect(self.cid_canvas_move) + # self.canvas.mpl_disconnect(self.cid_canvas_key) + # self.canvas.mpl_disconnect(self.cid_canvas_key_release) self.canvas.vis_disconnect('mouse_release', self.on_canvas_click) self.canvas.vis_disconnect('mouse_move', self.on_canvas_move) + self.canvas.vis_disconnect('key_press', self.on_canvas_key) + self.canvas.vis_disconnect('key_release', self.on_canvas_key_release) def add_shape(self, shape): """ @@ -1041,11 +1044,14 @@ class FlatCAMDraw(QtCore.QObject): :param event: :return: """ - self.key = event.key + + self.key = event.key.name + + print "key press", event.key.name ### Finish the current action. Use with tools that do not ### complete automatically, like a polygon or path. - if event.key == ' ': + if event.key.name == 'Space': if isinstance(self.active_tool, FCShapeTool): self.active_tool.click(self.snap(event.xdata, event.ydata)) self.active_tool.make() @@ -1055,7 +1061,7 @@ class FlatCAMDraw(QtCore.QObject): return ### Abort the current action - if event.key == 'escape': + if event.key.name == 'Escape': # TODO: ...? #self.on_tool_select("select") self.app.info("Cancelled.") @@ -1069,32 +1075,32 @@ class FlatCAMDraw(QtCore.QObject): return ### Delete selected object - if event.key == '-': + if event.key.name == '-': self.delete_selected() self.replot() ### Move - if event.key == 'm': + if event.key.name == 'M': self.move_btn.setChecked(True) self.on_tool_select('move') self.active_tool.set_origin(self.snap(event.xdata, event.ydata)) self.app.info("Click on target point.") ### Copy - if event.key == 'c': + if event.key.name == 'C': self.copy_btn.setChecked(True) self.on_tool_select('copy') self.active_tool.set_origin(self.snap(event.xdata, event.ydata)) self.app.info("Click on target point.") ### Snap - if event.key == 'g': + if event.key.name == 'G': self.grid_snap_btn.trigger() - if event.key == 'k': + if event.key.name == 'K': self.corner_snap_btn.trigger() ### Buffer - if event.key == 'b': + if event.key.name == 'B': self.on_buffer_tool() ### Propagate to tool From 1b86c6872ea70b274c555cfdb705547f5b9b0d14 Mon Sep 17 00:00:00 2001 From: HDR Date: Thu, 14 Jul 2016 15:45:48 +0600 Subject: [PATCH 15/26] Utility geometry clears on operation cancel --- FlatCAMDraw.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 2f63dc7..18974b3 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -819,9 +819,10 @@ class FlatCAMDraw(QtCore.QObject): self.snap_toolbar.setDisabled(True) # TODO: Combine and move into tool # Hide vispy visuals - self.shapes.parent = None - self.tool_shape.parent = None - self.cursor.parent = None + if self.shapes.parent is not None: + self.shapes.parent = None + self.tool_shape.parent = None + self.cursor.parent = None def delete_utility_geometry(self): #for_deletion = [shape for shape in self.shape_buffer if shape.utility] @@ -830,6 +831,9 @@ class FlatCAMDraw(QtCore.QObject): for shape in for_deletion: self.delete_shape(shape) + self.tool_shape.clear(update=True) + self.tool_shape.redraw() + def cutpath(self): selected = self.get_selected() tools = selected[1:] @@ -1044,11 +1048,8 @@ class FlatCAMDraw(QtCore.QObject): :param event: :return: """ - self.key = event.key.name - print "key press", event.key.name - ### Finish the current action. Use with tools that do not ### complete automatically, like a polygon or path. if event.key.name == 'Space': @@ -1209,8 +1210,6 @@ class FlatCAMDraw(QtCore.QObject): for shape in self.storage.get_objects(): - print "plot shape", shape - if shape.geo is None: # TODO: This shouldn't have happened continue From c55bc9c111211d2fcf96f07cbb27da05b5c62080 Mon Sep 17 00:00:00 2001 From: HDR Date: Thu, 14 Jul 2016 16:55:35 +0600 Subject: [PATCH 16/26] Work on zoom fit --- FlatCAMApp.py | 22 ++++++++++++---------- VisPyCanvas.py | 9 ++++++--- VisPyVisuals.py | 15 +++------------ 3 files changed, 21 insertions(+), 25 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 3c2b61b..271c3e1 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1460,6 +1460,7 @@ class App(QtCore.QObject): :return: None """ + print "plots updated" self.plotcanvas.auto_adjust_axes() self.on_zoom_fit(None) @@ -1501,7 +1502,7 @@ class App(QtCore.QObject): self.new_object_available.emit(obj) if plot: obj.plot() - self.on_zoom_fit(None) + # self.on_zoom_fit(None) t1 = time.time() # DEBUG self.log.debug("%f seconds adding object and plotting." % (t1 - t0)) @@ -1514,15 +1515,15 @@ class App(QtCore.QObject): :param event: Ignored. :return: None """ - - xmin, ymin, xmax, ymax = self.collection.get_bounds() - width = xmax - xmin - height = ymax - ymin - xmin -= 0.05 * width - xmax += 0.05 * width - ymin -= 0.05 * height - ymax += 0.05 * height - self.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax) + print "on_zoom_fit" + # xmin, ymin, xmax, ymax = self.collection.get_bounds() + # width = xmax - xmin + # height = ymax - ymin + # xmin -= 0.05 * width + # xmax += 0.05 * width + # ymin -= 0.05 * height + # ymax += 0.05 * height + # self.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax) self.plotcanvas.vispy_canvas.fit_view() @@ -2226,6 +2227,7 @@ class App(QtCore.QObject): self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True) # self.plot_all() + self.on_zoom_fit(None) self.inform.emit("Project loaded from: " + filename) App.log.debug("Project loaded") diff --git a/VisPyCanvas.py b/VisPyCanvas.py index bbc7a52..609cbbc 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -58,7 +58,10 @@ class VisPyCanvas(scene.SceneCanvas): return tr.map(pos) def fit_view(self): - rect = Rect() - rect.left, rect.right = self.view.get_scene_bounds(dim=0) - rect.bottom, rect.top = self.view.get_scene_bounds(dim=1) + rect = Rect(-1, -1, 10, 10) + try: + rect.left, rect.right = self.shape_collection.bounds(axis=0) + rect.bottom, rect.top = self.shape_collection.bounds(axis=1) + except TypeError: + pass self.view.camera.rect = rect diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 343efc4..6ca558b 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -35,7 +35,6 @@ class ShapeGroup(object): def redraw(self): t0 = time.time() self._collection.redraw() - print "group redraw time:", time.time() - t0 @property def visible(self): @@ -193,9 +192,6 @@ class ShapeCollectionVisual(CompoundVisual): def _update(self): - print "updating shape collection" - t0 = time.time() - mesh_vertices = [] # Vertices for mesh mesh_tris = [] # Faces for mesh mesh_colors = [] # Face colors @@ -228,16 +224,15 @@ class ShapeCollectionVisual(CompoundVisual): # Updating line if len(line_pts) > 0: self._line.set_data(np.asarray(line_pts), np.asarray(line_colors), self._line_width, 'segments') - - self.total_segments += len(line_pts) / 2 - else: self._line._bounds = None self._line._pos = None self._line._changed['pos'] = True self._line.update() - print "update completed:", time.time() - t0 + self._line._bounds_changed() + self._mesh._bounds_changed() + self._bounds_changed() def _open_ring(self, vertices): return vertices[:-1] if not np.any(vertices[0] != vertices[-1]) else vertices @@ -259,11 +254,7 @@ class ShapeCollectionVisual(CompoundVisual): def _linestring_to_segments(self, arr): return np.asarray(np.repeat(arr, 2, axis=0)[1:-1]) - def _compute_bounds(self, axis, view): - return self._line._compute_bounds(axis, view) - def redraw(self): - print "redrawing collection", len(self.data) self._update() From 4e9d577018f47cef6c1a96eff4c1b93d97f9848f Mon Sep 17 00:00:00 2001 From: Denvi Date: Thu, 14 Jul 2016 20:39:24 +0500 Subject: [PATCH 17/26] App crash on exit fix --- FlatCAMDraw.py | 4 ++-- FlatCAMObj.py | 3 +++ VisPyCanvas.py | 5 +++-- VisPyVisuals.py | 12 ++++++------ 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 18974b3..39592ab 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -1022,9 +1022,9 @@ class FlatCAMDraw(QtCore.QObject): # animated=True) try: for el in list(geo.geo): - self.tool_shape.add(el, color='blue', update=False) + self.tool_shape.add(el, color='#00000080', update=False) except TypeError: - self.tool_shape.add(geo.geo, color='blue', update=False) + self.tool_shape.add(geo.geo, color='#00000080', update=False) self.tool_shape.redraw() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 1273f39..6790ac3 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -53,6 +53,9 @@ class FlatCAMObj(QtCore.QObject): # self.ui.offset_button.clicked.connect(self.on_offset_button_click) # self.ui.scale_button.clicked.connect(self.on_scale_button_click) + def __del__(self): + pass + def from_dict(self, d): """ This supersedes ``from_dict`` in derived classes. Derived classes diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 609cbbc..0ea77da 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -60,8 +60,9 @@ class VisPyCanvas(scene.SceneCanvas): def fit_view(self): rect = Rect(-1, -1, 10, 10) try: - rect.left, rect.right = self.shape_collection.bounds(axis=0) - rect.bottom, rect.top = self.shape_collection.bounds(axis=1) + rect.left, rect.right = self.shape_collection.bounds(axis=0, view=self.shape_collection) + rect.bottom, rect.top = self.shape_collection.bounds(axis=1, view=self.shape_collection) except TypeError: pass self.view.camera.rect = rect + print "fit_view", rect diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 6ca558b..104f32e 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -27,13 +27,12 @@ class ShapeGroup(object): for i in self._indexes: self._collection.remove(i, False) - self._indexes = [] + del self._indexes[:] if update: self._collection.redraw() def redraw(self): - t0 = time.time() self._collection.redraw() @property @@ -54,7 +53,7 @@ class ShapeCollectionVisual(CompoundVisual): total_segments = 0 total_tris = 0 - def __init__(self, line_width=1, triangulation='vispy', **kwargs): + def __init__(self, line_width=1, triangulation='gpc', **kwargs): self.data = {} self.last_key = -1 @@ -181,12 +180,12 @@ class ShapeCollectionVisual(CompoundVisual): self.data[key]['mesh_colors'] = mesh_colors def remove(self, key, update=False): - del self.data[key] + self.data.pop(key) if update: self._update() def clear(self, update=False): - self.data = {} + self.data.clear() if update: self._update() @@ -213,6 +212,7 @@ class ShapeCollectionVisual(CompoundVisual): # Updating mesh if len(mesh_vertices) > 0: set_state(polygon_offset_fill=False) + print "set mesh data", len(mesh_vertices) self._mesh.set_data(np.asarray(mesh_vertices), np.asarray(mesh_tris, dtype=np.uint32), face_colors=np.asarray(mesh_colors)) @@ -230,7 +230,7 @@ class ShapeCollectionVisual(CompoundVisual): self._line._changed['pos'] = True self._line.update() - self._line._bounds_changed() + # self._line._bounds_changed() self._mesh._bounds_changed() self._bounds_changed() From 2afa296ddc99893a852b63226a4756c676406f6e Mon Sep 17 00:00:00 2001 From: Denvi Date: Thu, 14 Jul 2016 22:57:06 +0500 Subject: [PATCH 18/26] VisPy Grid patch --- FlatCAMApp.py | 2 +- FlatCAMDraw.py | 5 +++++ VisPyCanvas.py | 23 +++++++++++++++++++---- VisPyVisuals.py | 8 +++----- 4 files changed, 28 insertions(+), 10 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 271c3e1..7946d7a 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1502,7 +1502,7 @@ class App(QtCore.QObject): self.new_object_available.emit(obj) if plot: obj.plot() - # self.on_zoom_fit(None) + self.on_zoom_fit(None) t1 = time.time() # DEBUG self.log.debug("%f seconds adding object and plotting." % (t1 - t0)) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 39592ab..c99cd97 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -754,6 +754,8 @@ class FlatCAMDraw(QtCore.QObject): self.snap_max_dist_entry.editingFinished.connect(lambda: entry2option("snap_max", self.snap_max_dist_entry)) def activate(self): + + print "activate" parent = self.canvas.vispy_canvas.view.scene self.shapes.parent = parent self.tool_shape.parent = parent @@ -819,6 +821,9 @@ class FlatCAMDraw(QtCore.QObject): self.snap_toolbar.setDisabled(True) # TODO: Combine and move into tool # Hide vispy visuals + + print "deactivate" + if self.shapes.parent is not None: self.shapes.parent = None self.tool_shape.parent = None diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 0ea77da..1de2837 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -2,13 +2,29 @@ import numpy as np from PyQt4.QtGui import QPalette import vispy.scene as scene from vispy.geometry import Rect -from vispy.app.canvas import MouseEvent +from vispy.scene.widgets import Widget, Grid from VisPyVisuals import ShapeCollection +# Patch VisPy Grid to prevent updating layout on PaintGL +def _prepare_draw(self, view): + pass + +def _update_clipper(self): + super(Grid, self)._update_clipper() + try: + self._update_child_widget_dim() + except Exception as e: + print e + +Grid._prepare_draw = _prepare_draw +Grid._update_clipper = _update_clipper + + class VisPyCanvas(scene.SceneCanvas): def __init__(self, config=None): + scene.SceneCanvas.__init__(self, keys=None, config=config) self.unfreeze() @@ -60,9 +76,8 @@ class VisPyCanvas(scene.SceneCanvas): def fit_view(self): rect = Rect(-1, -1, 10, 10) try: - rect.left, rect.right = self.shape_collection.bounds(axis=0, view=self.shape_collection) - rect.bottom, rect.top = self.shape_collection.bounds(axis=1, view=self.shape_collection) + rect.left, rect.right = self.shape_collection.bounds(axis=0) + rect.bottom, rect.top = self.shape_collection.bounds(axis=1) except TypeError: pass self.view.camera.rect = rect - print "fit_view", rect diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 104f32e..baf3e92 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -67,9 +67,6 @@ class ShapeCollectionVisual(CompoundVisual): self._line.set_gl_state(blend=True) self.freeze() - def __del__(self): - print "ShapeCollection destructed" - def add(self, shape, color=None, face_color=None, visible=True, update=False): """Adds geometry object to collection @@ -80,6 +77,8 @@ class ShapeCollectionVisual(CompoundVisual): Line (polygon edge) color face_color: tuple Polygon fill color + visible: bool + Set to draw shape update: bool Set to redraw collection @@ -212,7 +211,6 @@ class ShapeCollectionVisual(CompoundVisual): # Updating mesh if len(mesh_vertices) > 0: set_state(polygon_offset_fill=False) - print "set mesh data", len(mesh_vertices) self._mesh.set_data(np.asarray(mesh_vertices), np.asarray(mesh_tris, dtype=np.uint32), face_colors=np.asarray(mesh_colors)) @@ -230,7 +228,7 @@ class ShapeCollectionVisual(CompoundVisual): self._line._changed['pos'] = True self._line.update() - # self._line._bounds_changed() + self._line._bounds_changed() self._mesh._bounds_changed() self._bounds_changed() From b25d941f352d676dae1b121b62a427b4653282c5 Mon Sep 17 00:00:00 2001 From: Denvi Date: Fri, 15 Jul 2016 00:10:30 +0500 Subject: [PATCH 19/26] Disable all/Enable all works with objects visibility now --- FlatCAMApp.py | 15 ++++++++------- ObjectCollection.py | 2 +- VisPyCanvas.py | 4 ++-- VisPyVisuals.py | 1 - 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 7946d7a..bc51a71 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -631,10 +631,12 @@ class App(QtCore.QObject): except ZeroDivisionError: self.progress.emit(0) return + for obj in self.collection.get_list(): if obj != self.collection.get_active() or not except_current: obj.options['plot'] = False - obj.plot() + # obj.plot() + obj.shapes.visible = False percentage += delta self.progress.emit(int(percentage*100)) @@ -1460,8 +1462,8 @@ class App(QtCore.QObject): :return: None """ - print "plots updated" self.plotcanvas.auto_adjust_axes() + self.plotcanvas.vispy_canvas.update() self.on_zoom_fit(None) def on_toolbar_replot(self): @@ -1515,7 +1517,6 @@ class App(QtCore.QObject): :param event: Ignored. :return: None """ - print "on_zoom_fit" # xmin, ymin, xmax, ymax = self.collection.get_bounds() # width = xmax - xmin # height = ymax - ymin @@ -2224,10 +2225,9 @@ class App(QtCore.QObject): def obj_init(obj_inst, app_inst): obj_inst.from_dict(obj) App.log.debug(obj['kind'] + ": " + obj['options']['name']) - self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=True) + self.new_object(obj['kind'], obj['options']['name'], obj_init, active=False, fit=False, plot=False) - # self.plot_all() - self.on_zoom_fit(None) + self.plot_all() self.inform.emit("Project loaded from: " + filename) App.log.debug("Project loaded") @@ -4165,7 +4165,8 @@ class App(QtCore.QObject): return for obj in self.collection.get_list(): obj.options['plot'] = True - obj.plot() + obj.shapes.visible = True + # obj.plot() percentage += delta self.progress.emit(int(percentage*100)) diff --git a/ObjectCollection.py b/ObjectCollection.py index 33d9557..cb9dd3d 100644 --- a/ObjectCollection.py +++ b/ObjectCollection.py @@ -297,7 +297,7 @@ class ObjectCollection(QtCore.QAbstractListModel): self.beginResetModel() for obj in self.object_list: - obj.shapes.clear(True) + obj.shapes.clear(obj == self.object_list[-1]) self.object_list = [] self.checked_indexes = [] diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 1de2837..da4daf3 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -74,10 +74,10 @@ class VisPyCanvas(scene.SceneCanvas): return tr.map(pos) def fit_view(self): - rect = Rect(-1, -1, 10, 10) + rect = Rect() try: rect.left, rect.right = self.shape_collection.bounds(axis=0) rect.bottom, rect.top = self.shape_collection.bounds(axis=1) + self.view.camera.rect = rect except TypeError: pass - self.view.camera.rect = rect diff --git a/VisPyVisuals.py b/VisPyVisuals.py index baf3e92..3e09d99 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -189,7 +189,6 @@ class ShapeCollectionVisual(CompoundVisual): self._update() def _update(self): - mesh_vertices = [] # Vertices for mesh mesh_tris = [] # Faces for mesh mesh_colors = [] # Face colors From cc592a219e052f0833396b9a6cd5e8b7ebfaffa0 Mon Sep 17 00:00:00 2001 From: HDR Date: Fri, 15 Jul 2016 09:24:41 +0600 Subject: [PATCH 20/26] Layers added --- FlatCAMDraw.py | 8 ++-- FlatCAMObj.py | 2 +- VisPyCanvas.py | 2 +- VisPyVisuals.py | 119 ++++++++++++++++++++++++++---------------------- 4 files changed, 70 insertions(+), 61 deletions(-) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index c99cd97..252e1b8 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -1027,9 +1027,9 @@ class FlatCAMDraw(QtCore.QObject): # animated=True) try: for el in list(geo.geo): - self.tool_shape.add(el, color='#00000080', update=False) + self.tool_shape.add(el, color='#FF000080', update=False) except TypeError: - self.tool_shape.add(geo.geo, color='#00000080', update=False) + self.tool_shape.add(geo.geo, color='#FF000080', update=False) self.tool_shape.redraw() @@ -1219,10 +1219,10 @@ class FlatCAMDraw(QtCore.QObject): continue if shape in self.selected: - self.plot_shape(geometry=shape.geo, color='red', linespec='k-', linewidth=2) + self.plot_shape(geometry=shape.geo, color='darkblue', linespec='k-', linewidth=2) continue - self.plot_shape(geometry=shape.geo, color='blue') + self.plot_shape(geometry=shape.geo, color='red') for shape in self.utility: self.plot_shape(geometry=shape.geo, linespec='k--', linewidth=1) diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 6790ac3..052222b 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -1500,7 +1500,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): except TypeError: # Element is not iterable... - self.shapes.add(element, color='#5E6CFFFF', visible=self.options['plot']) + self.shapes.add(element, color='red', visible=self.options['plot'], layer=0) # if type(element) == Polygon: # x, y = element.exterior.coords.xy diff --git a/VisPyCanvas.py b/VisPyCanvas.py index da4daf3..4b153c0 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -2,7 +2,7 @@ import numpy as np from PyQt4.QtGui import QPalette import vispy.scene as scene from vispy.geometry import Rect -from vispy.scene.widgets import Widget, Grid +from vispy.scene.widgets import Grid from VisPyVisuals import ShapeCollection diff --git a/VisPyVisuals.py b/VisPyVisuals.py index 3e09d99..f7e72cc 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -14,14 +14,23 @@ except: pass +# Patch LineVisual +def clear_data(self): + self._bounds = None + self._pos = None + self._changed['pos'] = True + self.update() + +LineVisual.clear_data = clear_data + class ShapeGroup(object): def __init__(self, collection): self._collection = collection self._indexes = [] self._visible = True - def add(self, shape, color=None, face_color=None, visible=True, update=False): - self._indexes.append(self._collection.add(shape, color, face_color, visible, update)) + def add(self, shape, color=None, face_color=None, visible=True, update=False, layer=1, order=0): + self._indexes.append(self._collection.add(shape, color, face_color, visible, update, layer, order)) def clear(self, update=False): for i in self._indexes: @@ -53,41 +62,33 @@ class ShapeCollectionVisual(CompoundVisual): total_segments = 0 total_tris = 0 - def __init__(self, line_width=1, triangulation='gpc', **kwargs): + def __init__(self, line_width=1, triangulation='gpc', layers=2, **kwargs): self.data = {} self.last_key = -1 - self._mesh = MeshVisual() - self._line = LineVisual(antialias=True) + self._meshes = [MeshVisual() for _ in range(0, layers)] + self._lines = [LineVisual(antialias=True) for _ in range(0, layers)] + self._line_width = line_width self._triangulation = triangulation - CompoundVisual.__init__(self, [self._mesh, self._line], **kwargs) - self._mesh.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) - self._line.set_gl_state(blend=True) + visuals = [self._lines[i / 2] if i % 2 else self._meshes[i / 2] for i in range(0, layers * 2)] + + CompoundVisual.__init__(self, visuals, **kwargs) + + for m in self._meshes: + m.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False) + + for l in self._lines: + l.set_gl_state(blend=True) + self.freeze() - def add(self, shape, color=None, face_color=None, visible=True, update=False): - """Adds geometry object to collection - - Args: - shape: shapely.geometry - Shapely geometry object - color: tuple - Line (polygon edge) color - face_color: tuple - Polygon fill color - visible: bool - Set to draw shape - update: bool - Set to redraw collection - - Returns: int - - """ + def add(self, shape, color=None, face_color=None, visible=True, update=False, layer=1, order=0): self.last_key += 1 - self.data[self.last_key] = {'geometry': shape, 'color': color, 'face_color': face_color, 'visible': visible} + self.data[self.last_key] = {'geometry': shape, 'color': color, 'face_color': face_color, + 'visible': visible, 'layer': layer, 'order': order} self.update_shape_buffers(self.last_key) if update: @@ -189,46 +190,54 @@ class ShapeCollectionVisual(CompoundVisual): self._update() def _update(self): - mesh_vertices = [] # Vertices for mesh - mesh_tris = [] # Faces for mesh - mesh_colors = [] # Face colors - line_pts = [] # Vertices for line - line_colors = [] # Line color + mesh_vertices = [[] for _ in range(0, len(self._meshes))] # Vertices for mesh + mesh_tris = [[] for _ in range(0, len(self._meshes))] # Faces for mesh + mesh_colors = [[] for _ in range(0, len(self._meshes))] # Face colors + line_pts = [[] for _ in range(0, len(self._lines))] # Vertices for line + line_colors = [[] for _ in range(0, len(self._lines))] # Line color # Merge shapes buffers for data in self.data.values(): if data['visible']: try: - line_pts += data['line_pts'] - line_colors += data['line_colors'] - mesh_tris += [[x + len(mesh_vertices) for x in y] for y in data['mesh_tris']] - mesh_vertices += data['mesh_vertices'] - mesh_colors += data['mesh_colors'] + line_pts[data['layer']] += data['line_pts'] + line_colors[data['layer']] += data['line_colors'] + mesh_tris[data['layer']] += [[x + len(mesh_vertices[data['layer']]) + for x in y] for y in data['mesh_tris']] + mesh_vertices[data['layer']] += data['mesh_vertices'] + mesh_colors[data['layer']] += data['mesh_colors'] except Exception as e: print "Data error", e - # Updating mesh - if len(mesh_vertices) > 0: - set_state(polygon_offset_fill=False) - self._mesh.set_data(np.asarray(mesh_vertices), np.asarray(mesh_tris, dtype=np.uint32), - face_colors=np.asarray(mesh_colors)) + # Updating meshes + for i, mesh in enumerate(self._meshes): + if len(mesh_vertices[i]) > 0: + set_state(polygon_offset_fill=False) + mesh.set_data(np.asarray(mesh_vertices[i]), np.asarray(mesh_tris[i], dtype=np.uint32), + face_colors=np.asarray(mesh_colors[i])) + else: + mesh.set_data() - self.total_tris += len(mesh_tris) + mesh._bounds_changed() - else: - self._mesh.set_data() + # Updating lines + for i, line in enumerate(self._lines): + if len(line_pts[i]) > 0: + line.set_data(np.asarray(line_pts[i]), np.asarray(line_colors[i]), self._line_width, 'segments') + else: + line.clear_data() - # Updating line - if len(line_pts) > 0: - self._line.set_data(np.asarray(line_pts), np.asarray(line_colors), self._line_width, 'segments') - else: - self._line._bounds = None - self._line._pos = None - self._line._changed['pos'] = True - self._line.update() + line._bounds_changed() - self._line._bounds_changed() - self._mesh._bounds_changed() + # if len(line_pts) > 0: + # self._line.set_data(np.asarray(line_pts), np.asarray(line_colors), self._line_width, 'segments') + # else: + # self._line._bounds = None + # self._line._pos = None + # self._line._changed['pos'] = True + # self._line.update() + + # self._line._bounds_changed() self._bounds_changed() def _open_ring(self, vertices): From 529c84061c962a01a6e46c5d0b49b8de963022c8 Mon Sep 17 00:00:00 2001 From: HDR Date: Fri, 15 Jul 2016 09:31:54 +0600 Subject: [PATCH 21/26] CNC job shows rapids now --- VisPyVisuals.py | 2 +- camlib.py | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/VisPyVisuals.py b/VisPyVisuals.py index f7e72cc..bb3cc46 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -62,7 +62,7 @@ class ShapeCollectionVisual(CompoundVisual): total_segments = 0 total_tris = 0 - def __init__(self, line_width=1, triangulation='gpc', layers=2, **kwargs): + def __init__(self, line_width=1, triangulation='gpc', layers=3, **kwargs): self.data = {} self.last_key = -1 diff --git a/camlib.py b/camlib.py index 1f59172..d1a92c8 100644 --- a/camlib.py +++ b/camlib.py @@ -3278,15 +3278,14 @@ class CNCjob(Geometry): # axes.annotate(str(path_num), xy=geo['geom'].coords[0], # xycoords='data') - if geo['kind'][0] == 'C': - poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance) - # patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0], - # edgecolor=color[geo['kind'][0]][1], - # alpha=alpha[geo['kind'][0]], zorder=2) - # axes.add_patch(patch) - # shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) - shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0], - visible=visible) + poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance) + # patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0], + # edgecolor=color[geo['kind'][0]][1], + # alpha=alpha[geo['kind'][0]], zorder=2) + # axes.add_patch(patch) + # shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) + shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0], + visible=visible, layer=1 if geo['kind'][0] == 'C' else 2) def create_geometry(self): # TODO: This takes forever. Too much data? From 205967fdc5ca39abe965f0c319e490eb8d9c4065 Mon Sep 17 00:00:00 2001 From: HDR Date: Fri, 15 Jul 2016 11:13:51 +0600 Subject: [PATCH 22/26] Objects fits in window on project loading --- FlatCAMApp.py | 14 +++++--- FlatCAMDraw.py | 16 +++++++--- FlatCAMObj.py | 24 +++++++++++--- PlotCanvas.py | 86 ++++++++++++++++++++++++++++++++------------------ VisPyCanvas.py | 76 ++++++++++++++++++++++++++++++++++++-------- camlib.py | 18 ++++++++--- 6 files changed, 171 insertions(+), 63 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index bc51a71..80eb50d 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -12,6 +12,7 @@ from PyQt4 import QtCore import time # Just used for debugging. Double check before removing. from xml.dom.minidom import parseString as parse_xml_string from contextlib import contextmanager +from vispy.geometry import Rect ######################################## ## Imports part of FlatCAM ## @@ -636,7 +637,7 @@ class App(QtCore.QObject): if obj != self.collection.get_active() or not except_current: obj.options['plot'] = False # obj.plot() - obj.shapes.visible = False + obj.visible = False percentage += delta self.progress.emit(int(percentage*100)) @@ -1504,7 +1505,8 @@ class App(QtCore.QObject): self.new_object_available.emit(obj) if plot: obj.plot() - self.on_zoom_fit(None) + self.on_zoom_fit(None) + t1 = time.time() # DEBUG self.log.debug("%f seconds adding object and plotting." % (t1 - t0)) @@ -1526,8 +1528,9 @@ class App(QtCore.QObject): # ymax += 0.05 * height # self.plotcanvas.adjust_axes(xmin, ymin, xmax, ymax) - self.plotcanvas.vispy_canvas.fit_view() + self.plotcanvas.fit_view() + # TODO: Rewrite for VisPy event def on_key_over_plot(self, event): """ Callback for the key pressed event when the canvas is focused. Keyboard @@ -1565,6 +1568,7 @@ class App(QtCore.QObject): # self.inform.emit("Measuring tool OFF") # return + # TODO: Rewrite for VisPy event def on_click_over_plot(self, event): """ Callback for the mouse click event over the plot. This event is generated @@ -2293,6 +2297,8 @@ class App(QtCore.QObject): return for obj in self.collection.get_list(): obj.plot() + self.plotcanvas.fit_view() # Fit in proper thread + percentage += delta self.progress.emit(int(percentage*100)) @@ -4165,7 +4171,7 @@ class App(QtCore.QObject): return for obj in self.collection.get_list(): obj.options['plot'] = True - obj.shapes.visible = True + obj.visible = True # obj.plot() percentage += delta self.progress.emit(int(percentage*100)) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 252e1b8..80f9b26 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -592,6 +592,7 @@ class FlatCAMDraw(QtCore.QObject): self.app = app self.canvas = app.plotcanvas + self.fcgeometry = None # self.axes = self.canvas.new_axes("draw") ### Drawing Toolbar ### @@ -821,14 +822,15 @@ class FlatCAMDraw(QtCore.QObject): self.snap_toolbar.setDisabled(True) # TODO: Combine and move into tool # Hide vispy visuals - - print "deactivate" - if self.shapes.parent is not None: self.shapes.parent = None self.tool_shape.parent = None self.cursor.parent = None + # Show original geometry + if self.fcgeometry: + self.fcgeometry.visible = True + def delete_utility_geometry(self): #for_deletion = [shape for shape in self.shape_buffer if shape.utility] #for_deletion = [shape for shape in self.storage.get_objects() if shape.utility] @@ -881,6 +883,10 @@ class FlatCAMDraw(QtCore.QObject): self.deactivate() self.activate() + # Hide original geometry + self.fcgeometry = fcgeometry + fcgeometry.visible = False + self.connect_canvas_event_handlers() self.select_tool("select") @@ -1059,7 +1065,7 @@ class FlatCAMDraw(QtCore.QObject): ### complete automatically, like a polygon or path. if event.key.name == 'Space': if isinstance(self.active_tool, FCShapeTool): - self.active_tool.click(self.snap(event.xdata, event.ydata)) + self.active_tool.click(self.snap(event.xdata, event.ydata)) # TODO: Get coordinates self.active_tool.make() if self.active_tool.complete: self.on_shape_complete() @@ -1219,7 +1225,7 @@ class FlatCAMDraw(QtCore.QObject): continue if shape in self.selected: - self.plot_shape(geometry=shape.geo, color='darkblue', linespec='k-', linewidth=2) + self.plot_shape(geometry=shape.geo, color='blue', linespec='k-', linewidth=2) continue self.plot_shape(geometry=shape.geo, color='red') diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 052222b..0b91cff 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -286,6 +286,18 @@ class FlatCAMObj(QtCore.QObject): """ return + @property + def visible(self): + return self.shapes.visible + + @visible.setter + def visible(self, value): + self.shapes.visible = value + try: + self.annotation.parent = self.app.plotcanvas.vispy_canvas.view.scene \ + if (value and not self.annotation.parent) else None + except: + pass class FlatCAMGerber(FlatCAMObj, Gerber): """ @@ -556,7 +568,7 @@ class FlatCAMGerber(FlatCAMObj, Gerber): if self.muted_ui: return self.read_form_item('plot') - self.shapes.visible = self.options['plot'] + self.visible = self.options['plot'] # self.plot() def on_solid_cb_click(self, *args): @@ -954,7 +966,7 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): if self.muted_ui: return self.read_form_item('plot') - self.shapes.visible = self.options['plot'] + self.visible = self.options['plot'] # self.plot() def on_solid_cb_click(self, *args): @@ -1043,6 +1055,8 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): # from predecessors. self.ser_attrs += ['options', 'kind'] + # self.annotation = self.app.plotcanvas.new_annotation() + def set_ui(self, ui): FlatCAMObj.set_ui(self, ui) @@ -1155,7 +1169,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if self.muted_ui: return self.read_form_item('plot') - self.shapes.visible = self.options['plot'] + self.visible = self.options['plot'] # self.plot() def plot(self): @@ -1165,7 +1179,7 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if not FlatCAMObj.plot(self): return - self.plot2(self.axes, tooldia=self.options["tooldia"], shapes=self.shapes, visible=self.options['plot']) + self.plot2(self.axes, tooldia=self.options["tooldia"], obj=self, visible=self.options['plot']) self.shapes.redraw() # self.app.plotcanvas.auto_adjust_axes() @@ -1438,7 +1452,7 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): if self.muted_ui: return self.read_form_item('plot') - self.shapes.visible = self.options['plot'] + self.visible = self.options['plot'] # self.plot() def scale(self, factor): diff --git a/PlotCanvas.py b/PlotCanvas.py index 8100436..e059479 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -19,8 +19,9 @@ import FlatCAMApp import logging from VisPyCanvas import VisPyCanvas from VisPyVisuals import ShapeGroup, ShapeCollection -from vispy.scene.visuals import Markers +from vispy.scene.visuals import Markers, Text import numpy as np +from vispy.geometry import Rect log = logging.getLogger('base') @@ -161,6 +162,13 @@ class PlotCanvas(QtCore.QObject): self.vispy_canvas.native.setParent(self.app.ui) self.container.addWidget(self.vispy_canvas.native) + self.shape_collection = self.new_shape_collection() + self.shape_collection.parent = self.vispy_canvas.view.scene + # self.annotation = self.new_annotation() + # self.annotation.text = ['test1', 'test2'] + # self.annotation.pos = [(0, 0), (10, 10)] + + # Copy a bitmap of the canvas for quick animation. # Update every time the canvas is re-drawn. self.background = self.canvas.copy_from_bbox(self.axes.bbox) @@ -359,36 +367,38 @@ class PlotCanvas(QtCore.QObject): :return: None """ - xmin, xmax = self.axes.get_xlim() - ymin, ymax = self.axes.get_ylim() - width = xmax - xmin - height = ymax - ymin + self.vispy_canvas.view.camera.zoom(factor, center) - if center is None or center == [None, None]: - center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0] - - # For keeping the point at the pointer location - relx = (xmax - center[0]) / width - rely = (ymax - center[1]) / height - - new_width = width / factor - new_height = height / factor - - xmin = center[0] - new_width * (1 - relx) - xmax = center[0] + new_width * relx - ymin = center[1] - new_height * (1 - rely) - ymax = center[1] + new_height * rely - - # Adjust axes - for ax in self.figure.get_axes(): - ax.set_xlim((xmin, xmax)) - ax.set_ylim((ymin, ymax)) - - # Async re-draw - self.canvas.draw_idle() - - ##### Temporary place-holder for cached update ##### - self.update_screen_request.emit([0, 0, 0, 0, 0]) + # xmin, xmax = self.axes.get_xlim() + # ymin, ymax = self.axes.get_ylim() + # width = xmax - xmin + # height = ymax - ymin + # + # if center is None or center == [None, None]: + # center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0] + # + # # For keeping the point at the pointer location + # relx = (xmax - center[0]) / width + # rely = (ymax - center[1]) / height + # + # new_width = width / factor + # new_height = height / factor + # + # xmin = center[0] - new_width * (1 - relx) + # xmax = center[0] + new_width * relx + # ymin = center[1] - new_height * (1 - rely) + # ymax = center[1] + new_height * rely + # + # # Adjust axes + # for ax in self.figure.get_axes(): + # ax.set_xlim((xmin, xmax)) + # ax.set_ylim((ymin, ymax)) + # + # # Async re-draw + # self.canvas.draw_idle() + # + # ##### Temporary place-holder for cached update ##### + # self.update_screen_request.emit([0, 0, 0, 0, 0]) def pan(self, x, y): xmin, xmax = self.axes.get_xlim() @@ -419,7 +429,7 @@ class PlotCanvas(QtCore.QObject): return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name) def new_shape_group(self): - return ShapeGroup(self.vispy_canvas.shape_collection) + return ShapeGroup(self.shape_collection) # TODO: Make local shape collection def new_shape_collection(self): return ShapeCollection() @@ -427,6 +437,9 @@ class PlotCanvas(QtCore.QObject): def new_cursor(self): return Markers(pos=np.empty((0, 2))) + def new_annotation(self): + return Text(parent=self.vispy_canvas.view.scene) + def on_scroll(self, event): """ Scroll event handler. @@ -546,3 +559,14 @@ class PlotCanvas(QtCore.QObject): height = ymax - ymin return width / xpx, height / ypx + + def fit_view(self, rect=None): + if not rect: + rect = Rect(0, 0, 10, 10) + try: + rect.left, rect.right = self.shape_collection.bounds(axis=0) + rect.bottom, rect.top = self.shape_collection.bounds(axis=1) + except TypeError: + pass + + self.vispy_canvas.view.camera.rect = rect diff --git a/VisPyCanvas.py b/VisPyCanvas.py index 4b153c0..255951f 100644 --- a/VisPyCanvas.py +++ b/VisPyCanvas.py @@ -1,9 +1,8 @@ import numpy as np from PyQt4.QtGui import QPalette import vispy.scene as scene -from vispy.geometry import Rect from vispy.scene.widgets import Grid -from VisPyVisuals import ShapeCollection +from vispy.scene.cameras.base_camera import BaseCamera # Patch VisPy Grid to prevent updating layout on PaintGL @@ -40,7 +39,7 @@ class VisPyCanvas(scene.SceneCanvas): top_padding.height_max = 24 yaxis = scene.AxisWidget(orientation='left', axis_color='black', text_color='black', font_size=12) - yaxis.width_max = 60 + yaxis.width_max = 50 grid.add_widget(yaxis, row=1, col=0) xaxis = scene.AxisWidget(orientation='bottom', axis_color='black', text_color='black', font_size=12) @@ -51,7 +50,7 @@ class VisPyCanvas(scene.SceneCanvas): right_padding.width_max = 24 view = grid.add_view(row=1, col=1, border_color='black', bgcolor='white') - view.camera = scene.PanZoomCamera(aspect=1) + view.camera = Camera(aspect=1) grid1 = scene.GridLines(parent=view.scene, color='gray') grid1.set_gl_state(depth_test=False) @@ -64,7 +63,6 @@ class VisPyCanvas(scene.SceneCanvas): self.grid = grid1 self.view = view - self.shape_collection = ShapeCollection(parent=view.scene) self.freeze() self.measure_fps() @@ -73,11 +71,63 @@ class VisPyCanvas(scene.SceneCanvas): tr = self.grid.get_transform('canvas', 'visual') return tr.map(pos) - def fit_view(self): - rect = Rect() - try: - rect.left, rect.right = self.shape_collection.bounds(axis=0) - rect.bottom, rect.top = self.shape_collection.bounds(axis=1) - self.view.camera.rect = rect - except TypeError: - pass + +class Camera(scene.PanZoomCamera): + def zoom(self, factor, center=None): + center = center if (center is not None) else self.center + super(Camera, self).zoom(factor, center) + + def viewbox_mouse_event(self, event): + """ + The SubScene received a mouse event; update transform + accordingly. + + Parameters + ---------- + event : instance of Event + The event. + """ + if event.handled or not self.interactive: + return + + # Scrolling + BaseCamera.viewbox_mouse_event(self, event) + + if event.type == 'mouse_wheel': + center = self._scene_transform.imap(event.pos) + self.zoom((1 + self.zoom_factor) ** (-event.delta[1] * 30), center) + event.handled = True + + elif event.type == 'mouse_move': + if event.press_event is None: + return + + modifiers = event.mouse_event.modifiers + p1 = event.mouse_event.press_event.pos + p2 = event.mouse_event.pos + + if event.button in [2, 3] and not modifiers: + # Translate + p1 = np.array(event.last_event.pos)[:2] + p2 = np.array(event.pos)[:2] + p1s = self._transform.imap(p1) + p2s = self._transform.imap(p2) + self.pan(p1s-p2s) + event.handled = True + elif event.button in [2, 3] and 'Shift' in modifiers: + # Zoom + p1c = np.array(event.last_event.pos)[:2] + p2c = np.array(event.pos)[:2] + scale = ((1 + self.zoom_factor) ** + ((p1c-p2c) * np.array([1, -1]))) + center = self._transform.imap(event.press_event.pos[:2]) + self.zoom(scale, center) + event.handled = True + else: + event.handled = False + elif event.type == 'mouse_press': + # accept the event if it is button 1 or 2. + # This is required in order to receive future events + event.handled = event.button in [1, 2, 3] + else: + event.handled = False \ No newline at end of file diff --git a/camlib.py b/camlib.py index d1a92c8..d01c373 100644 --- a/camlib.py +++ b/camlib.py @@ -3245,7 +3245,7 @@ class CNCjob(Geometry): def plot2(self, axes, tooldia=None, dpi=75, margin=0.1, color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]}, - alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, shapes=None, visible=False): + alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, obj=None, visible=False): """ Plots the G-code job onto the given axes. @@ -3271,21 +3271,29 @@ class CNCjob(Geometry): # linespec = 'k-' # x, y = geo['geom'].coords.xy # axes.plot(x, y, linespec, color=linecolor) - shapes.add(geo['geom'], color=linecolor, visible=visible) + obj.shapes.add(geo['geom'], color=linecolor, visible=visible) else: + text = [] + pos = [] for geo in self.gcode_parsed: - # path_num += 1 + path_num += 1 # axes.annotate(str(path_num), xy=geo['geom'].coords[0], # xycoords='data') + text.append(str(path_num)) + pos.append(geo['geom'].coords[0]) + poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance) # patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0], # edgecolor=color[geo['kind'][0]][1], # alpha=alpha[geo['kind'][0]], zorder=2) # axes.add_patch(patch) # shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) - shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0], - visible=visible, layer=1 if geo['kind'][0] == 'C' else 2) + obj.shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0], + visible=visible, layer=1 if geo['kind'][0] == 'C' else 2) + + # obj.annotation.text = text + # obj.annotation.pos = pos def create_geometry(self): # TODO: This takes forever. Too much data? From 2de6e6a22a5431a4d24ba33d0cc095e4a1c3e634 Mon Sep 17 00:00:00 2001 From: HDR Date: Fri, 15 Jul 2016 11:30:45 +0600 Subject: [PATCH 23/26] Keyboard, mouse events fixed --- FlatCAMApp.py | 48 ++++++++++++++++++++++-------------------------- FlatCAMDraw.py | 13 +++++++++---- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 80eb50d..ac20007 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -189,11 +189,13 @@ class App(QtCore.QObject): #### Plot Area #### # self.plotcanvas = PlotCanvas(self.ui.splitter) self.plotcanvas = PlotCanvas(self.ui.right_layout, self) - self.plotcanvas.mpl_connect('button_press_event', self.on_click_over_plot) - self.plotcanvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot) - self.plotcanvas.mpl_connect('key_press_event', self.on_key_over_plot) + # self.plotcanvas.mpl_connect('button_press_event', self.on_click_over_plot) + # self.plotcanvas.mpl_connect('motion_notify_event', self.on_mouse_move_over_plot) + # self.plotcanvas.mpl_connect('key_press_event', self.on_key_over_plot) - self.plotcanvas.vis_connect('mouse_move', self.on_mouse_move) + self.plotcanvas.vis_connect('mouse_move', self.on_mouse_move_over_plot) + self.plotcanvas.vis_connect('mouse_release', self.on_click_over_plot) + self.plotcanvas.vis_connect('key_press', self.on_key_over_plot) self.ui.splitter.setStretchFactor(1, 2) @@ -1585,13 +1587,17 @@ class App(QtCore.QObject): """ # So it can receive key presses - self.plotcanvas.canvas.setFocus() + self.plotcanvas.vispy_canvas.native.setFocus() + + pos = self.plotcanvas.vispy_canvas.translate_coords(event.pos) try: App.log.debug('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % ( - event.button, event.x, event.y, event.xdata, event.ydata)) + event.button, event.pos[0], event.pos[1], pos[0], pos[1])) + # App.log.debug('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % ( + # event.button, event.x, event.y, event.xdata, event.ydata)) - self.clipboard.setText(self.defaults["point_clipboard_format"] % (event.xdata, event.ydata)) + self.clipboard.setText(self.defaults["point_clipboard_format"] % (event.pos[0], event.pos[1])) except Exception, e: App.log.debug("Outside plot?") @@ -1607,25 +1613,6 @@ class App(QtCore.QObject): :return: None """ - try: # May fail in case mouse not within axes - self.ui.position_label.setText("X: %.4f Y: %.4f" % ( - event.xdata, event.ydata)) - self.mouse = [event.xdata, event.ydata] - - except: - self.ui.position_label.setText("") - self.mouse = None - - def on_mouse_move(self, event): - """ - Callback for the mouse motion event over the plot. This event is generated - by the Matplotlib backend and has been registered in ``self.__init__()``. - For details, see: http://matplotlib.org/users/event_handling.html - - :param event: Contains information about the event. - :return: None - """ - pos = self.plotcanvas.vispy_canvas.translate_coords(event.pos) try: # May fail in case mouse not within axes @@ -1637,6 +1624,15 @@ class App(QtCore.QObject): self.ui.position_label.setText("") self.mouse = None + # try: # May fail in case mouse not within axes + # self.ui.position_label.setText("X: %.4f Y: %.4f" % ( + # event.xdata, event.ydata)) + # self.mouse = [event.xdata, event.ydata] + # + # except: + # self.ui.position_label.setText("") + # self.mouse = None + def on_file_new(self): """ Callback for menu item File->New. Returns the application to its diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index 80f9b26..d4a1dd1 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -592,7 +592,6 @@ class FlatCAMDraw(QtCore.QObject): self.app = app self.canvas = app.plotcanvas - self.fcgeometry = None # self.axes = self.canvas.new_axes("draw") ### Drawing Toolbar ### @@ -701,6 +700,7 @@ class FlatCAMDraw(QtCore.QObject): self.utility = [] # VisPy visuals + self.fcgeometry = None self.shapes = self.app.plotcanvas.new_shape_collection() self.tool_shape = self.app.plotcanvas.new_shape_collection() self.cursor = self.app.plotcanvas.new_cursor() @@ -712,6 +712,8 @@ class FlatCAMDraw(QtCore.QObject): self.move_timer.setSingleShot(True) self.key = None # Currently pressed key + self.x = None # Current mouse cursor pos + self.y = None def make_callback(thetool): def f(): @@ -968,6 +970,9 @@ class FlatCAMDraw(QtCore.QObject): pos = self.canvas.vispy_canvas.translate_coords(event.pos) event.xdata, event.ydata = pos[0], pos[1] + self.x = event.xdata + self.y = event.ydata + self.on_canvas_move_effective(event) return None @@ -1065,7 +1070,7 @@ class FlatCAMDraw(QtCore.QObject): ### complete automatically, like a polygon or path. if event.key.name == 'Space': if isinstance(self.active_tool, FCShapeTool): - self.active_tool.click(self.snap(event.xdata, event.ydata)) # TODO: Get coordinates + self.active_tool.click(self.snap(self.x, self.y)) # TODO: Get coordinates self.active_tool.make() if self.active_tool.complete: self.on_shape_complete() @@ -1095,14 +1100,14 @@ class FlatCAMDraw(QtCore.QObject): if event.key.name == 'M': self.move_btn.setChecked(True) self.on_tool_select('move') - self.active_tool.set_origin(self.snap(event.xdata, event.ydata)) + self.active_tool.set_origin(self.snap(self.x, self.y)) self.app.info("Click on target point.") ### Copy if event.key.name == 'C': self.copy_btn.setChecked(True) self.on_tool_select('copy') - self.active_tool.set_origin(self.snap(event.xdata, event.ydata)) + self.active_tool.set_origin(self.snap(self.x, self.y)) self.app.info("Click on target point.") ### Snap From 7afaad626ee28996dafd8aeda523e8f7c99fa067 Mon Sep 17 00:00:00 2001 From: HDR Date: Fri, 15 Jul 2016 12:45:01 +0600 Subject: [PATCH 24/26] Cleanup --- FlatCAMApp.py | 2 -- FlatCAMDraw.py | 2 +- FlatCAMObj.py | 2 +- VisPyVisuals.py | 33 ++++++++++++++------------------- 4 files changed, 16 insertions(+), 23 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index ac20007..81eec23 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1532,7 +1532,6 @@ class App(QtCore.QObject): self.plotcanvas.fit_view() - # TODO: Rewrite for VisPy event def on_key_over_plot(self, event): """ Callback for the key pressed event when the canvas is focused. Keyboard @@ -1570,7 +1569,6 @@ class App(QtCore.QObject): # self.inform.emit("Measuring tool OFF") # return - # TODO: Rewrite for VisPy event def on_click_over_plot(self, event): """ Callback for the mouse click event over the plot. This event is generated diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index d4a1dd1..c14dffc 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -1070,7 +1070,7 @@ class FlatCAMDraw(QtCore.QObject): ### complete automatically, like a polygon or path. if event.key.name == 'Space': if isinstance(self.active_tool, FCShapeTool): - self.active_tool.click(self.snap(self.x, self.y)) # TODO: Get coordinates + self.active_tool.click(self.snap(self.x, self.y)) self.active_tool.make() if self.active_tool.complete: self.on_shape_complete() diff --git a/FlatCAMObj.py b/FlatCAMObj.py index 0b91cff..ab3c6c9 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -40,7 +40,7 @@ class FlatCAMObj(QtCore.QObject): self.form_fields = {} - self.axes = None # Matplotlib axes + # self.axes = None # Matplotlib axes self.kind = None # Override with proper name # self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene) diff --git a/VisPyVisuals.py b/VisPyVisuals.py index bb3cc46..2219080 100644 --- a/VisPyVisuals.py +++ b/VisPyVisuals.py @@ -5,7 +5,6 @@ from vispy.geometry.triangulation import Triangulation from vispy.color import Color from shapely.geometry import Polygon, LineString, LinearRing import numpy as np -import time try: from shapely.ops import triangulate @@ -14,7 +13,7 @@ except: pass -# Patch LineVisual +# Add clear_data method to LineVisual def clear_data(self): self._bounds = None self._pos = None @@ -23,6 +22,7 @@ def clear_data(self): LineVisual.clear_data = clear_data + class ShapeGroup(object): def __init__(self, collection): self._collection = collection @@ -229,39 +229,34 @@ class ShapeCollectionVisual(CompoundVisual): line._bounds_changed() - # if len(line_pts) > 0: - # self._line.set_data(np.asarray(line_pts), np.asarray(line_colors), self._line_width, 'segments') - # else: - # self._line._bounds = None - # self._line._pos = None - # self._line._changed['pos'] = True - # self._line.update() - - # self._line._bounds_changed() self._bounds_changed() - def _open_ring(self, vertices): + def redraw(self): + self._update() + + @staticmethod + def _open_ring(vertices): return vertices[:-1] if not np.any(vertices[0] != vertices[-1]) else vertices - def _generate_edges(self, count): + @staticmethod + def _generate_edges(count): edges = np.empty((count, 2), dtype=np.uint32) edges[:, 0] = np.arange(count) edges[:, 1] = edges[:, 0] + 1 edges[-1, 1] = 0 return edges - def _linearring_to_segments(self, arr): + @staticmethod + def _linearring_to_segments(arr): # Close linear ring if np.any(arr[0] != arr[-1]): arr = np.concatenate([arr, arr[:1]], axis=0) - return self._linestring_to_segments(arr) + return ShapeCollection._linestring_to_segments(arr) - def _linestring_to_segments(self, arr): + @staticmethod + def _linestring_to_segments(arr): return np.asarray(np.repeat(arr, 2, axis=0)[1:-1]) - def redraw(self): - self._update() - ShapeCollection = create_visual_node(ShapeCollectionVisual) From d0f32769623c7fa28896d6945eb7eeb8c50fdaa9 Mon Sep 17 00:00:00 2001 From: HDR Date: Fri, 15 Jul 2016 13:22:59 +0600 Subject: [PATCH 25/26] Cleanup --- FlatCAMApp.py | 4 +- FlatCAMDraw.py | 109 ++-------- FlatCAMObj.py | 167 +--------------- MeasurementTool.py | 3 +- PlotCanvas.py | 480 +-------------------------------------------- camlib.py | 21 +- 6 files changed, 36 insertions(+), 748 deletions(-) diff --git a/FlatCAMApp.py b/FlatCAMApp.py index 81eec23..a1f2199 100644 --- a/FlatCAMApp.py +++ b/FlatCAMApp.py @@ -1448,7 +1448,7 @@ class App(QtCore.QObject): # Remove plot # self.plotcanvas.figure.delaxes(self.collection.get_active().axes) - self.plotcanvas.auto_adjust_axes() + # self.plotcanvas.auto_adjust_axes() # Clear form self.setup_component_editor() @@ -1465,7 +1465,7 @@ class App(QtCore.QObject): :return: None """ - self.plotcanvas.auto_adjust_axes() + # self.plotcanvas.auto_adjust_axes() self.plotcanvas.vispy_canvas.update() self.on_zoom_fit(None) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index c14dffc..df96841 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -592,7 +592,6 @@ class FlatCAMDraw(QtCore.QObject): self.app = app self.canvas = app.plotcanvas - # self.axes = self.canvas.new_axes("draw") ### Drawing Toolbar ### self.drawing_toolbar = QtGui.QToolBar() @@ -959,39 +958,6 @@ class FlatCAMDraw(QtCore.QObject): self.app.log.debug("No active tool to respond to click!") def on_canvas_move(self, event): - """ - event.x and .y have canvas coordinates - event.xdaya and .ydata have plot coordinates - - :param event: Event object dispatched by Matplotlib - :return: - """ - - pos = self.canvas.vispy_canvas.translate_coords(event.pos) - event.xdata, event.ydata = pos[0], pos[1] - - self.x = event.xdata - self.y = event.ydata - - self.on_canvas_move_effective(event) - return None - - # self.move_timer.stop() - # - # if self.active_tool is None: - # return - # - # # Make a function to avoid late evaluation - # def make_callback(): - # def f(): - # self.on_canvas_move_effective(event) - # return f - # callback = make_callback() - # - # self.move_timer.timeout.connect(callback) - # self.move_timer.start(500) # Stops if aready running - - def on_canvas_move_effective(self, event): """ Is called after timeout on timer set in on_canvas_move. @@ -1005,6 +971,12 @@ class FlatCAMDraw(QtCore.QObject): :return: None """ + pos = self.canvas.vispy_canvas.translate_coords(event.pos) + event.xdata, event.ydata = pos[0], pos[1] + + self.x = event.xdata + self.y = event.ydata + try: x = float(event.xdata) y = float(event.ydata) @@ -1018,45 +990,23 @@ class FlatCAMDraw(QtCore.QObject): x, y = self.snap(x, y) ### Utility geometry (animated) - self.canvas.canvas.restore_region(self.canvas.background) geo = self.active_tool.utility_geometry(data=(x, y)) if isinstance(geo, DrawToolShape) and geo.geo is not None: # Remove any previous utility shape - # self.delete_utility_geometry() self.tool_shape.clear(update=True) # Add the new utility shape - # self.add_shape(geo) - - # Efficient plotting for fast animation - - #self.canvas.canvas.restore_region(self.canvas.background) - # elements = self.plot_shape(geometry=geo.geo, - # linespec="b--", - # linewidth=1, - # animated=True) try: for el in list(geo.geo): self.tool_shape.add(el, color='#FF000080', update=False) except TypeError: self.tool_shape.add(geo.geo, color='#FF000080', update=False) - self.tool_shape.redraw() - # for el in elements: - # self.axes.draw_artist(el) - #self.canvas.canvas.blit(self.axes.bbox) - - # Pointer (snapped) - # elements = self.axes.plot(x, y, 'bo', animated=True) - # for el in elements: - # self.axes.draw_artist(el) - + # Update cursor self.cursor.set_data(np.asarray([(x, y)]), symbol='s', edge_color='red', size=5) - # self.canvas.canvas.blit(self.axes.bbox) - def on_canvas_key(self, event): """ event.key has the key. @@ -1150,7 +1100,7 @@ class FlatCAMDraw(QtCore.QObject): self.selected = [] - def plot_shape(self, geometry=None, color='black', linespec='b-', linewidth=1, animated=False): + def plot_shape(self, geometry=None, color='black',linewidth=1): """ Plots a geometric object or list of objects without rendering. Plotted objects are returned as a list. This allows for efficient/animated rendering. @@ -1158,7 +1108,6 @@ class FlatCAMDraw(QtCore.QObject): :param geometry: Geometry to be plotted (Any Shapely.geom kind or list of such) :param linespec: Matplotlib linespec string. :param linewidth: Width of lines in # of pixels. - :param animated: If geometry is to be animated. (See MPL plot()) :return: List of plotted elements. """ plot_elements = [] @@ -1168,60 +1117,36 @@ class FlatCAMDraw(QtCore.QObject): try: for geo in geometry: - plot_elements += self.plot_shape(geometry=geo, - color=color, - linespec=linespec, - linewidth=linewidth, - animated=animated) + plot_elements += self.plot_shape(geometry=geo, color=color, linewidth=linewidth) ## Non-iterable except TypeError: ## DrawToolShape if isinstance(geometry, DrawToolShape): - plot_elements += self.plot_shape(geometry=geometry.geo, - color=color, - linespec=linespec, - linewidth=linewidth, - animated=animated) + plot_elements += self.plot_shape(geometry=geometry.geo, color=color, linewidth=linewidth) - ## Polygon: Dscend into exterior and each interior. + ## Polygon: Descend into exterior and each interior. if type(geometry) == Polygon: - plot_elements += self.plot_shape(geometry=geometry.exterior, - color=color, - linespec=linespec, - linewidth=linewidth, - animated=animated) - plot_elements += self.plot_shape(geometry=geometry.interiors, - color=color, - linespec=linespec, - linewidth=linewidth, - animated=animated) + plot_elements += self.plot_shape(geometry=geometry.exterior, color=color, linewidth=linewidth) + plot_elements += self.plot_shape(geometry=geometry.interiors, color=color, linewidth=linewidth) if type(geometry) == LineString or type(geometry) == LinearRing: - # x, y = geometry.coords.xy - # element, = self.axes.plot(x, y, linespec, linewidth=linewidth, animated=animated) - self.shapes.add(geometry, color=color) - # plot_elements.append(element) + plot_elements.append(self.shapes.add(geometry, color=color)) if type(geometry) == Point: pass - # x, y = geometry.coords.xy - # element, = self.axes.plot(x, y, 'bo', linewidth=linewidth, animated=animated) - # plot_elements.append(element) return plot_elements def plot_all(self): """ Plots all shapes in the editor. - Clears the axes, plots, and call self.canvas.auto_adjust_axes. :return: None :rtype: None """ self.app.log.debug("plot_all()") - # self.axes.cla() self.shapes.clear(update=True) for shape in self.storage.get_objects(): @@ -1230,17 +1155,16 @@ class FlatCAMDraw(QtCore.QObject): continue if shape in self.selected: - self.plot_shape(geometry=shape.geo, color='blue', linespec='k-', linewidth=2) + self.plot_shape(geometry=shape.geo, color='blue', linewidth=2) continue self.plot_shape(geometry=shape.geo, color='red') for shape in self.utility: - self.plot_shape(geometry=shape.geo, linespec='k--', linewidth=1) + self.plot_shape(geometry=shape.geo, linewidth=1) continue self.shapes.redraw() - # self.canvas.auto_adjust_axes() def on_shape_complete(self): self.app.log.debug("on_shape_complete()") @@ -1268,7 +1192,6 @@ class FlatCAMDraw(QtCore.QObject): self.selected.remove(shape) def replot(self): - # self.axes = self.canvas.new_axes("draw") self.plot_all() @staticmethod diff --git a/FlatCAMObj.py b/FlatCAMObj.py index ab3c6c9..65ce913 100644 --- a/FlatCAMObj.py +++ b/FlatCAMObj.py @@ -40,7 +40,6 @@ class FlatCAMObj(QtCore.QObject): self.form_fields = {} - # self.axes = None # Matplotlib axes self.kind = None # Override with proper name # self.shapes = ShapeCollection(parent=self.app.plotcanvas.vispy_canvas.view.scene) @@ -110,38 +109,6 @@ class FlatCAMObj(QtCore.QObject): self.scale(factor) self.plot() - def setup_axes(self, figure): - """ - 1) Creates axes if they don't exist. 2) Clears axes. 3) Attaches - them to figure if not part of the figure. 4) Sets transparent - background. 5) Sets 1:1 scale aspect ratio. - - :param figure: A Matplotlib.Figure on which to add/configure axes. - :type figure: matplotlib.figure.Figure - :return: None - :rtype: None - """ - - # if self.axes is None: - # FlatCAMApp.App.log.debug("setup_axes(): New axes") - # self.axes = figure.add_axes([0.05, 0.05, 0.9, 0.9], - # label=self.options["name"]) - # elif self.axes not in figure.axes: - # FlatCAMApp.App.log.debug("setup_axes(): Clearing and attaching axes") - # self.axes.cla() - # figure.add_axes(self.axes) - # else: - # FlatCAMApp.App.log.debug("setup_axes(): Clearing Axes") - # self.axes.cla() - # - # # Remove all decoration. The app's axes will have - # # the ticks and grid. - # self.axes.set_frame_on(False) # No frame - # self.axes.set_xticks([]) # No tick - # self.axes.set_yticks([]) # No ticks - # self.axes.patch.set_visible(False) # No background - # self.axes.set_aspect(1) - def to_form(self): """ Copies options to the UI form. @@ -243,7 +210,6 @@ class FlatCAMObj(QtCore.QObject): def plot(self): """ Plot this object (Extend this method to implement the actual plotting). - Axes get created, appended to canvas and cleared before plotting. Call this in descendants before doing the plotting. :return: Whether to continue plotting or not depending on the "plot" option. @@ -251,18 +217,6 @@ class FlatCAMObj(QtCore.QObject): """ FlatCAMApp.App.log.debug(str(inspect.stack()[1][3]) + " --> FlatCAMObj.plot()") - # Axes must exist and be attached to canvas. - # if self.axes is None or self.axes not in self.app.plotcanvas.figure.axes: - # self.axes = self.app.plotcanvas.new_axes(self.options['name']) - - # if not self.options["plot"]: - # # self.axes.cla() - # # self.app.plotcanvas.auto_adjust_axes() - # self.clear_shapes(update=True) - # return False - - # Clear axes or we will plot on top of them. - # self.axes.cla() # TODO: Thread safe? self.shapes.clear() return True @@ -619,50 +573,20 @@ class FlatCAMGerber(FlatCAMObj, Gerber): except TypeError: geometry = [geometry] - # if self.options["multicolored"]: - # linespec = '-' - # else: - # linespec = 'k-' + def random_color(): + color = np.random.rand(4) + color[3] = 1 + return color if self.options["solid"]: for poly in geometry: - if self.options["multicolored"]: - face_color = np.random.rand(4) - face_color[3] = 1 - else: - face_color = '#BBF268BF' - - self.shapes.add(poly, color='#006E20BF', face_color=face_color, visible=self.options['plot']) - - # # TODO: Too many things hardcoded. - # try: - # patch = PolygonPatch(poly, - # facecolor="#BBF268", - # edgecolor="#006E20", - # alpha=0.75, - # zorder=2) - # self.axes.add_patch(patch) - # except AssertionError: - # FlatCAMApp.App.log.warning("A geometry component was not a polygon:") - # FlatCAMApp.App.log.warning(str(poly)) + self.shapes.add(poly, color='#006E20BF', face_color=random_color() if self.options['multicolored'] else + '#BBF268BF', visible=self.options['plot']) else: for poly in geometry: - if self.options["multicolored"]: - color = np.random.rand(4) - color[3] = 1 - else: - color = 'black' - - self.shapes.add(poly, color=color, visible=self.options['plot']) - - # x, y = poly.exterior.xy - # self.axes.plot(x, y, linespec) - # for ints in poly.interiors: - # x, y = ints.coords.xy - # self.axes.plot(x, y, linespec) - + self.shapes.add(poly, color=random_color() if self.options['multicolored'] else 'black', + visible=self.options['plot']) self.shapes.redraw() - # self.app.plotcanvas.auto_adjust_axes() def serialize(self): return { @@ -998,27 +922,13 @@ class FlatCAMExcellon(FlatCAMObj, Excellon): if self.options["solid"]: for geo in self.solid_geometry: self.shapes.add(geo, color='#750000BF', face_color='#C40000BF', visible=self.options['plot']) - - # patch = PolygonPatch(geo, - # facecolor="#C40000", - # edgecolor="#750000", - # alpha=0.75, - # zorder=3) - # self.axes.add_patch(patch) else: for geo in self.solid_geometry: self.shapes.add(geo.exterior, color='red', visible=self.options['plot']) - - # x, y = geo.exterior.coords.xy - # self.axes.plot(x, y, 'r-') for ints in geo.interiors: self.shapes.add(ints, color='green', visible=self.options['plot']) - # x, y = ints.coords.xy - # self.axes.plot(x, y, 'g-') self.shapes.redraw() - # self.app.plotcanvas.auto_adjust_axes() - class FlatCAMCNCjob(FlatCAMObj, CNCjob): """ @@ -1179,10 +1089,9 @@ class FlatCAMCNCjob(FlatCAMObj, CNCjob): if not FlatCAMObj.plot(self): return - self.plot2(self.axes, tooldia=self.options["tooldia"], obj=self, visible=self.options['plot']) + self.plot2(tooldia=self.options["tooldia"], obj=self, visible=self.options['plot']) self.shapes.redraw() - # self.app.plotcanvas.auto_adjust_axes() def convert_units(self, units): factor = CNCjob.convert_units(self, units) @@ -1513,29 +1422,11 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): self.plot_element(sub_el) except TypeError: # Element is not iterable... - self.shapes.add(element, color='red', visible=self.options['plot'], layer=0) - # if type(element) == Polygon: - # x, y = element.exterior.coords.xy - # self.axes.plot(x, y, 'r-') - # for ints in element.interiors: - # x, y = ints.coords.xy - # self.axes.plot(x, y, 'r-') - # return - # - # if type(element) == LineString or type(element) == LinearRing: - # self.app.plotcanvas.vispy_canvas.shapes.add(element) - # x, y = element.coords.xy - # self.axes.plot(x, y, 'r-') - # return - # - # FlatCAMApp.App.log.warning("Did not plot:" + str(type(element))) - def plot(self): """ - Plots the object into its axes. If None, of if the axes - are not part of the app's figure, it fetches new ones. + Adds the object into collection. :return: None """ @@ -1545,43 +1436,5 @@ class FlatCAMGeometry(FlatCAMObj, Geometry): if not FlatCAMObj.plot(self): return - # Make sure solid_geometry is iterable. - # TODO: This method should not modify the object !!! - # try: - # _ = iter(self.solid_geometry) - # except TypeError: - # if self.solid_geometry is None: - # self.solid_geometry = [] - # else: - # self.solid_geometry = [self.solid_geometry] - # - # for geo in self.solid_geometry: - # - # if type(geo) == Polygon: - # x, y = geo.exterior.coords.xy - # self.axes.plot(x, y, 'r-') - # for ints in geo.interiors: - # x, y = ints.coords.xy - # self.axes.plot(x, y, 'r-') - # continue - # - # if type(geo) == LineString or type(geo) == LinearRing: - # x, y = geo.coords.xy - # self.axes.plot(x, y, 'r-') - # continue - # - # if type(geo) == MultiPolygon: - # for poly in geo: - # x, y = poly.exterior.coords.xy - # self.axes.plot(x, y, 'r-') - # for ints in poly.interiors: - # x, y = ints.coords.xy - # self.axes.plot(x, y, 'r-') - # continue - # - # FlatCAMApp.App.log.warning("Did not plot:", str(type(geo))) - self.plot_element(self.solid_geometry) self.shapes.redraw() - - # self.app.plotcanvas.auto_adjust_axes() diff --git a/MeasurementTool.py b/MeasurementTool.py index f8396d0..12327f3 100644 --- a/MeasurementTool.py +++ b/MeasurementTool.py @@ -32,7 +32,8 @@ class Measurement(FlatCAMTool): def install(self): FlatCAMTool.install(self) self.app.ui.right_layout.addWidget(self) - self.app.plotcanvas.mpl_connect('key_press_event', self.on_key_press) + # TODO: Translate to vis + # self.app.plotcanvas.mpl_connect('key_press_event', self.on_key_press) def run(self): self.toggle() diff --git a/PlotCanvas.py b/PlotCanvas.py index e059479..5d6bd83 100644 --- a/PlotCanvas.py +++ b/PlotCanvas.py @@ -6,16 +6,8 @@ # MIT Licence # ############################################################ -from PyQt4 import QtGui, QtCore +from PyQt4 import QtCore -# Prevent conflict with Qt5 and above. -from matplotlib import use as mpl_use -mpl_use("Qt4Agg") - -from matplotlib.figure import Figure -from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas -from matplotlib.backends.backend_agg import FigureCanvasAgg -import FlatCAMApp import logging from VisPyCanvas import VisPyCanvas from VisPyVisuals import ShapeGroup, ShapeCollection @@ -26,93 +18,11 @@ from vispy.geometry import Rect log = logging.getLogger('base') -class CanvasCache(QtCore.QObject): - """ - - Case story #1: - - 1) No objects in the project. - 2) Object is created (new_object() emits object_created(obj)). - on_object_created() adds (i) object to collection and emits - (ii) new_object_available() then calls (iii) object.plot() - 3) object.plot() creates axes if necessary on - app.collection.figure. Then plots on it. - 4) Plots on a cache-size canvas (in background). - 5) Plot completes. Bitmap is generated. - 6) Visible canvas is painted. - - """ - - # Signals: - # A bitmap is ready to be displayed. - new_screen = QtCore.pyqtSignal() - - def __init__(self, plotcanvas, app, dpi=50): - - super(CanvasCache, self).__init__() - - self.app = app - - self.plotcanvas = plotcanvas - self.dpi = dpi - - self.figure = Figure(dpi=dpi) - - self.axes = self.figure.add_axes([0.0, 0.0, 1.0, 1.0], alpha=1.0) - self.axes.set_frame_on(False) - self.axes.set_xticks([]) - self.axes.set_yticks([]) - - self.canvas = FigureCanvasAgg(self.figure) - - self.cache = None - - def run(self): - - log.debug("CanvasCache Thread Started!") - - self.plotcanvas.update_screen_request.connect(self.on_update_req) - - self.app.new_object_available.connect(self.on_new_object_available) - - def on_update_req(self, extents): - """ - Event handler for an updated display request. - - :param extents: [xmin, xmax, ymin, ymax, zoom(optional)] - """ - - log.debug("Canvas update requested: %s" % str(extents)) - - # Note: This information below might be out of date. Establish - # a protocol regarding when to change the canvas in the main - # thread and when to check these values here in the background, - # or pass this data in the signal (safer). - log.debug("Size: %s [px]" % str(self.plotcanvas.get_axes_pixelsize())) - log.debug("Density: %s [units/px]" % str(self.plotcanvas.get_density())) - - # Move the requested screen portion to the main thread - # and inform about the update: - - self.new_screen.emit() - - # Continue to update the cache. - - def on_new_object_available(self): - - log.debug("A new object is available. Should plot it!") - - class PlotCanvas(QtCore.QObject): """ Class handling the plotting area in the application. """ - # Signals: - # Request for new bitmap to display. The parameter - # is a list with [xmin, xmax, ymin, ymax, zoom(optional)] - update_screen_request = QtCore.pyqtSignal(list) - def __init__(self, container, app): """ The constructor configures the Matplotlib figure that @@ -134,29 +44,7 @@ class PlotCanvas(QtCore.QObject): # Parent container self.container = container - # Plots go onto a single matplotlib.figure - self.figure = Figure(dpi=50) # TODO: dpi needed? - self.figure.patch.set_visible(False) - - # These axes show the ticks and grid. No plotting done here. - # New axes must have a label, otherwise mpl returns an existing one. - self.axes = self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label="base", alpha=0.0) - self.axes.set_aspect(1) - self.axes.grid(True) - - # The canvas is the top level container (FigureCanvasQTAgg) - self.canvas = FigureCanvas(self.figure) - # self.canvas.setFocusPolicy(QtCore.Qt.ClickFocus) - # self.canvas.setFocus() - - #self.canvas.set_hexpand(1) - #self.canvas.set_vexpand(1) - #self.canvas.set_can_focus(True) # For key press - # Attach to parent - #self.container.attach(self.canvas, 0, 0, 600, 400) # TODO: Height and width are num. columns?? - # self.container.addWidget(self.canvas) # Qt - conf = {'samples': 8} self.vispy_canvas = VisPyCanvas() self.vispy_canvas.create_native() self.vispy_canvas.native.setParent(self.app.ui) @@ -164,63 +52,6 @@ class PlotCanvas(QtCore.QObject): self.shape_collection = self.new_shape_collection() self.shape_collection.parent = self.vispy_canvas.view.scene - # self.annotation = self.new_annotation() - # self.annotation.text = ['test1', 'test2'] - # self.annotation.pos = [(0, 0), (10, 10)] - - - # Copy a bitmap of the canvas for quick animation. - # Update every time the canvas is re-drawn. - self.background = self.canvas.copy_from_bbox(self.axes.bbox) - - ### Bitmap Cache - self.cache = CanvasCache(self, self.app) - self.cache_thread = QtCore.QThread() - self.cache.moveToThread(self.cache_thread) - super(PlotCanvas, self).connect(self.cache_thread, QtCore.SIGNAL("started()"), self.cache.run) - # self.connect() - self.cache_thread.start() - self.cache.new_screen.connect(self.on_new_screen) - - # Events - self.canvas.mpl_connect('button_press_event', self.on_mouse_press) - self.canvas.mpl_connect('button_release_event', self.on_mouse_release) - self.canvas.mpl_connect('motion_notify_event', self.on_mouse_move) - #self.canvas.connect('configure-event', self.auto_adjust_axes) - self.canvas.mpl_connect('resize_event', self.auto_adjust_axes) - #self.canvas.add_events(Gdk.EventMask.SMOOTH_SCROLL_MASK) - #self.canvas.connect("scroll-event", self.on_scroll) - self.canvas.mpl_connect('scroll_event', self.on_scroll) - self.canvas.mpl_connect('key_press_event', self.on_key_down) - self.canvas.mpl_connect('key_release_event', self.on_key_up) - self.canvas.mpl_connect('draw_event', self.on_draw) - - self.mouse = [0, 0] - self.key = None - - self.pan_axes = [] - self.panning = False - - def on_new_screen(self): - - log.debug("Cache updated the screen!") - - def on_key_down(self, event): - """ - - :param event: - :return: - """ - FlatCAMApp.App.log.debug('on_key_down(): ' + str(event.key)) - self.key = event.key - - def on_key_up(self, event): - """ - - :param event: - :return: - """ - self.key = None def vis_connect(self, event_name, callback): return getattr(self.vispy_canvas.events, event_name).connect(callback) @@ -228,133 +59,6 @@ class PlotCanvas(QtCore.QObject): def vis_disconnect(self, event_name, callback): getattr(self.vispy_canvas.events, event_name).disconnect(callback) - def mpl_connect(self, event_name, callback): - """ - Attach an event handler to the canvas through the Matplotlib interface. - - :param event_name: Name of the event - :type event_name: str - :param callback: Function to call - :type callback: func - :return: Connection id - :rtype: int - """ - return self.canvas.mpl_connect(event_name, callback) - - def mpl_disconnect(self, cid): - """ - Disconnect callback with the give id. - :param cid: Callback id. - :return: None - """ - self.canvas.mpl_disconnect(cid) - - def connect(self, event_name, callback): - """ - Attach an event handler to the canvas through the native Qt interface. - - :param event_name: Name of the event - :type event_name: str - :param callback: Function to call - :type callback: function - :return: Nothing - """ - self.canvas.connect(event_name, callback) - - def clear(self): - """ - Clears axes and figure. - - :return: None - """ - - # Clear - self.axes.cla() - try: - self.figure.clf() - except KeyError: - FlatCAMApp.App.log.warning("KeyError in MPL figure.clf()") - - # Re-build - self.figure.add_axes(self.axes) - self.axes.set_aspect(1) - self.axes.grid(True) - - # Re-draw - self.canvas.draw_idle() - - def adjust_axes(self, xmin, ymin, xmax, ymax): - """ - Adjusts all axes while maintaining the use of the whole canvas - and an aspect ratio to 1:1 between x and y axes. The parameters are an original - request that will be modified to fit these restrictions. - - :param xmin: Requested minimum value for the X axis. - :type xmin: float - :param ymin: Requested minimum value for the Y axis. - :type ymin: float - :param xmax: Requested maximum value for the X axis. - :type xmax: float - :param ymax: Requested maximum value for the Y axis. - :type ymax: float - :return: None - """ - - # FlatCAMApp.App.log.debug("PC.adjust_axes()") - - width = xmax - xmin - height = ymax - ymin - try: - r = width / height - except ZeroDivisionError: - FlatCAMApp.App.log.error("Height is %f" % height) - return - canvas_w, canvas_h = self.canvas.get_width_height() - canvas_r = float(canvas_w) / canvas_h - x_ratio = float(self.x_margin) / canvas_w - y_ratio = float(self.y_margin) / canvas_h - - if r > canvas_r: - ycenter = (ymin + ymax) / 2.0 - newheight = height * r / canvas_r - ymin = ycenter - newheight / 2.0 - ymax = ycenter + newheight / 2.0 - else: - xcenter = (xmax + xmin) / 2.0 - newwidth = width * canvas_r / r - xmin = xcenter - newwidth / 2.0 - xmax = xcenter + newwidth / 2.0 - - # Adjust axes - for ax in self.figure.get_axes(): - if ax._label != 'base': - ax.set_frame_on(False) # No frame - ax.set_xticks([]) # No tick - ax.set_yticks([]) # No ticks - ax.patch.set_visible(False) # No background - ax.set_aspect(1) - ax.set_xlim((xmin, xmax)) - ax.set_ylim((ymin, ymax)) - ax.set_position([x_ratio, y_ratio, 1 - 2 * x_ratio, 1 - 2 * y_ratio]) - - # Sync re-draw to proper paint on form resize - self.canvas.draw() - - ##### Temporary place-holder for cached update ##### - self.update_screen_request.emit([0, 0, 0, 0, 0]) - - def auto_adjust_axes(self, *args): - """ - Calls ``adjust_axes()`` using the extents of the base axes. - - :rtype : None - :return: None - """ - - xmin, xmax = self.axes.get_xlim() - ymin, ymax = self.axes.get_ylim() - self.adjust_axes(xmin, ymin, xmax, ymax) - def zoom(self, factor, center=None): """ Zooms the plot by factor around a given @@ -369,65 +73,6 @@ class PlotCanvas(QtCore.QObject): self.vispy_canvas.view.camera.zoom(factor, center) - # xmin, xmax = self.axes.get_xlim() - # ymin, ymax = self.axes.get_ylim() - # width = xmax - xmin - # height = ymax - ymin - # - # if center is None or center == [None, None]: - # center = [(xmin + xmax) / 2.0, (ymin + ymax) / 2.0] - # - # # For keeping the point at the pointer location - # relx = (xmax - center[0]) / width - # rely = (ymax - center[1]) / height - # - # new_width = width / factor - # new_height = height / factor - # - # xmin = center[0] - new_width * (1 - relx) - # xmax = center[0] + new_width * relx - # ymin = center[1] - new_height * (1 - rely) - # ymax = center[1] + new_height * rely - # - # # Adjust axes - # for ax in self.figure.get_axes(): - # ax.set_xlim((xmin, xmax)) - # ax.set_ylim((ymin, ymax)) - # - # # Async re-draw - # self.canvas.draw_idle() - # - # ##### Temporary place-holder for cached update ##### - # self.update_screen_request.emit([0, 0, 0, 0, 0]) - - def pan(self, x, y): - xmin, xmax = self.axes.get_xlim() - ymin, ymax = self.axes.get_ylim() - width = xmax - xmin - height = ymax - ymin - - # Adjust axes - for ax in self.figure.get_axes(): - ax.set_xlim((xmin + x * width, xmax + x * width)) - ax.set_ylim((ymin + y * height, ymax + y * height)) - - # Re-draw - self.canvas.draw_idle() - - ##### Temporary place-holder for cached update ##### - self.update_screen_request.emit([0, 0, 0, 0, 0]) - - def new_axes(self, name): - """ - Creates and returns an Axes object attached to this object's Figure. - - :param name: Unique label for the axes. - :return: Axes attached to the figure. - :rtype: Axes - """ - - return self.figure.add_axes([0.05, 0.05, 0.9, 0.9], label=name) - def new_shape_group(self): return ShapeGroup(self.shape_collection) # TODO: Make local shape collection @@ -440,126 +85,6 @@ class PlotCanvas(QtCore.QObject): def new_annotation(self): return Text(parent=self.vispy_canvas.view.scene) - def on_scroll(self, event): - """ - Scroll event handler. - - :param event: Event object containing the event information. - :return: None - """ - - # So it can receive key presses - # self.canvas.grab_focus() - self.canvas.setFocus() - - # Event info - # z, direction = event.get_scroll_direction() - - if self.key is None: - - if event.button == 'up': - self.zoom(1.5, self.mouse) - else: - self.zoom(1 / 1.5, self.mouse) - return - - if self.key == 'shift': - - if event.button == 'up': - self.pan(0.3, 0) - else: - self.pan(-0.3, 0) - return - - if self.key == 'control': - - if event.button == 'up': - self.pan(0, 0.3) - else: - self.pan(0, -0.3) - return - - def on_mouse_press(self, event): - - # Check for middle mouse button press - if event.button == 2: - - # Prepare axes for pan (using 'matplotlib' pan function) - self.pan_axes = [] - for a in self.figure.get_axes(): - if (event.x is not None and event.y is not None and a.in_axes(event) and - a.get_navigate() and a.can_pan()): - a.start_pan(event.x, event.y, 1) - self.pan_axes.append(a) - - # Set pan view flag - if len(self.pan_axes) > 0: self.panning = True; - - def on_mouse_release(self, event): - - # Check for middle mouse button release to complete pan procedure - if event.button == 2: - for a in self.pan_axes: - a.end_pan() - - # Clear pan flag - self.panning = False - - def on_mouse_move(self, event): - """ - Mouse movement event hadler. Stores the coordinates. Updates view on pan. - - :param event: Contains information about the event. - :return: None - """ - self.mouse = [event.xdata, event.ydata] - - # Update pan view on mouse move - if self.panning is True: - for a in self.pan_axes: - a.drag_pan(1, event.key, event.x, event.y) - - # Async re-draw (redraws only on thread idle state, uses timer on backend) - self.canvas.draw_idle() - - ##### Temporary place-holder for cached update ##### - self.update_screen_request.emit([0, 0, 0, 0, 0]) - - def on_draw(self, renderer): - - # Store background on canvas redraw - self.background = self.canvas.copy_from_bbox(self.axes.bbox) - - def get_axes_pixelsize(self): - """ - Axes size in pixels. - - :return: Pixel width and height - :rtype: tuple - """ - bbox = self.axes.get_window_extent().transformed(self.figure.dpi_scale_trans.inverted()) - width, height = bbox.width, bbox.height - width *= self.figure.dpi - height *= self.figure.dpi - return width, height - - def get_density(self): - """ - Returns unit length per pixel on horizontal - and vertical axes. - - :return: X and Y density - :rtype: tuple - """ - xpx, ypx = self.get_axes_pixelsize() - - xmin, xmax = self.axes.get_xlim() - ymin, ymax = self.axes.get_ylim() - width = xmax - xmin - height = ymax - ymin - - return width / xpx, height / ypx - def fit_view(self, rect=None): if not rect: rect = Rect(0, 0, 10, 10) @@ -570,3 +95,6 @@ class PlotCanvas(QtCore.QObject): pass self.vispy_canvas.view.camera.rect = rect + + def clear(self): + pass \ No newline at end of file diff --git a/camlib.py b/camlib.py index d01c373..0f78322 100644 --- a/camlib.py +++ b/camlib.py @@ -13,7 +13,6 @@ from cStringIO import StringIO from numpy import arctan2, Inf, array, sqrt, pi, ceil, sin, cos, dot, float32, \ transpose from numpy.linalg import solve, norm -from matplotlib.figure import Figure import re import sys import traceback @@ -21,8 +20,6 @@ from decimal import Decimal import collections import numpy as np -import matplotlib -#import matplotlib.pyplot as plt #from scipy.spatial import Delaunay, KDTree from rtree import index as rtindex @@ -3243,13 +3240,12 @@ class CNCjob(Geometry): # # return fig - def plot2(self, axes, tooldia=None, dpi=75, margin=0.1, + def plot2(self, tooldia=None, dpi=75, margin=0.1, color={"T": ["#F0E24D4C", "#B5AB3A4C"], "C": ["#5E6CFFFF", "#4650BDFF"]}, alpha={"T": 0.3, "C": 1.0}, tool_tolerance=0.0005, obj=None, visible=False): """ Plots the G-code job onto the given axes. - :param axes: Matplotlib axes on which to plot. :param tooldia: Tool diameter. :param dpi: Not used! :param margin: Not used! @@ -3265,30 +3261,17 @@ class CNCjob(Geometry): if tooldia == 0: for geo in self.gcode_parsed: - # linespec = '--' - linecolor = color[geo['kind'][0]][1] - # if geo['kind'][0] == 'C': - # linespec = 'k-' - # x, y = geo['geom'].coords.xy - # axes.plot(x, y, linespec, color=linecolor) - obj.shapes.add(geo['geom'], color=linecolor, visible=visible) + obj.shapes.add(geo['geom'], color=color[geo['kind'][0]][1], visible=visible) else: text = [] pos = [] for geo in self.gcode_parsed: path_num += 1 - # axes.annotate(str(path_num), xy=geo['geom'].coords[0], - # xycoords='data') text.append(str(path_num)) pos.append(geo['geom'].coords[0]) poly = geo['geom'].buffer(tooldia / 2.0).simplify(tool_tolerance) - # patch = PolygonPatch(poly, facecolor=color[geo['kind'][0]][0], - # edgecolor=color[geo['kind'][0]][1], - # alpha=alpha[geo['kind'][0]], zorder=2) - # axes.add_patch(patch) - # shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0]) obj.shapes.add(poly, color=color[geo['kind'][0]][1], face_color=color[geo['kind'][0]][0], visible=visible, layer=1 if geo['kind'][0] == 'C' else 2) From 68986cd909c40086a1fa8d5850644d7d48c1c4ac Mon Sep 17 00:00:00 2001 From: HDR Date: Fri, 15 Jul 2016 14:00:33 +0600 Subject: [PATCH 26/26] Cleanup --- FlatCAMDraw.py | 14 +++++++------- camlib.py | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/FlatCAMDraw.py b/FlatCAMDraw.py index df96841..f963e15 100644 --- a/FlatCAMDraw.py +++ b/FlatCAMDraw.py @@ -959,15 +959,11 @@ class FlatCAMDraw(QtCore.QObject): def on_canvas_move(self, event): """ - Is called after timeout on timer set in on_canvas_move. + Called on 'mouse_move' event - For details on animating on MPL see: - http://wiki.scipy.org/Cookbook/Matplotlib/Animations + event.pos have canvas screen coordinates - event.x and .y have canvas coordinates - event.xdaya and .ydata have plot coordinates - - :param event: Event object dispatched by Matplotlib + :param event: Event object dispatched by VisPy SceneCavas :return: None """ @@ -977,6 +973,10 @@ class FlatCAMDraw(QtCore.QObject): self.x = event.xdata self.y = event.ydata + # Prevent updates on pan + if len(event.buttons) > 0: + return + try: x = float(event.xdata) y = float(event.ydata) diff --git a/camlib.py b/camlib.py index 0f78322..69e3430 100644 --- a/camlib.py +++ b/camlib.py @@ -39,7 +39,6 @@ from descartes.patch import PolygonPatch import simplejson as json # TODO: Commented for FlatCAM packaging with cx_freeze -#from matplotlib.pyplot import plot, subplot import xml.etree.ElementTree as ET # from svg.path import Path, Line, Arc, CubicBezier, QuadraticBezier, parse_path