Files
FlatCAM/VisPyVisuals.py
2016-07-20 09:25:26 +06:00

341 lines
11 KiB
Python

from vispy.visuals import CompoundVisual, LineVisual, MeshVisual
from vispy.scene.visuals import create_visual_node
from vispy.gloo import set_state
from vispy.color import Color
from shapely.geometry import Polygon, LineString, LinearRing
from multiprocessing import Pool
import threading
import numpy as np
from VisPyTesselators import GLUTess
def _update_shape_buffers(data, triangulation='glu'):
"""
Translates Shapely geometry to internal buffers for speedup redraws
:param data: dict
Input shape data
:param triangulation: str
Triangulation engine
"""
mesh_vertices = [] # Vertices for mesh
mesh_tris = [] # Faces for mesh
mesh_colors = [] # Face colors
line_pts = [] # Vertices for line
line_colors = [] # Line color
geo, color, face_color, tolerance = data['geometry'], data['color'], data['face_color'], data['tolerance']
if geo is not None and not geo.is_empty:
simple = geo.simplify(tolerance) if tolerance else geo # Simplified shape
pts = [] # Shape line points
tri_pts = [] # Mesh vertices
tri_tris = [] # Mesh faces
if type(geo) == LineString:
# Prepare lines
pts = _linestring_to_segments(list(simple.coords))
elif type(geo) == LinearRing:
# Prepare lines
pts = _linearring_to_segments(list(simple.coords))
elif type(geo) == Polygon:
# Prepare polygon faces
if face_color is not None:
if triangulation == 'glu':
gt = GLUTess()
tri_tris, tri_pts = gt.triangulate(simple)
else:
print "Triangulation type '%s' isn't implemented. Drawing only edges." % triangulation
# Prepare polygon edges
if color is not None:
pts = _linearring_to_segments(list(simple.exterior.coords))
for ints in simple.interiors:
pts += _linearring_to_segments(list(ints.coords))
# Appending data for mesh
if len(tri_pts) > 0 and len(tri_tris) > 0:
mesh_tris += tri_tris
mesh_vertices += tri_pts
mesh_colors += [Color(face_color).rgba] * (len(tri_tris) / 3)
# Appending data for line
if len(pts) > 0:
line_pts += pts
line_colors += [Color(color).rgba] * len(pts)
# Store buffers
data['line_pts'] = line_pts
data['line_colors'] = line_colors
data['mesh_vertices'] = mesh_vertices
data['mesh_tris'] = mesh_tris
data['mesh_colors'] = mesh_colors
# Clear shapely geometry
del data['geometry']
return data
def _linearring_to_segments(arr):
# Close linear ring
"""
Translates linear ring to line segments
:param arr: numpy.array
Array of linear ring vertices
:return: numpy.array
Line segments
"""
if arr[0] != arr[-1]:
arr.append(arr[0])
return _linestring_to_segments(arr)
def _linestring_to_segments(arr):
"""
Translates line strip to segments
:param arr: numpy.array
Array of line strip vertices
:return: numpy.array
Line segments
"""
return [arr[i / 2] for i in range(0, len(arr) * 2)][1:-1]
class ShapeGroup(object):
def __init__(self, collection):
"""
Represents group of shapes in collection
:param collection: ShapeCollection
Collection to work with
"""
self._collection = collection
self._indexes = []
self._results = {}
self._visible = True
def add(self, **kwargs):
"""
Adds shape to collection and store index in group
:param kwargs: keyword arguments
Arguments for ShapeCollection.add function
"""
self._indexes.append(self._collection.add(**kwargs))
def clear(self, update=False):
"""
Removes group shapes from collection, clear indexes
:param update: bool
Set True to redraw collection
"""
for i in self._indexes:
self._collection.remove(i, False)
del self._indexes[:]
if update:
self._collection.redraw([]) # Skip waiting results
def redraw(self):
"""
Redraws shape collection
"""
self._collection.redraw(self._indexes)
@property
def visible(self):
"""
Visibility of group
:return: bool
"""
return self._visible
@visible.setter
def visible(self, value):
"""
Visibility of group
:param value: bool
"""
self._visible = value
for i in self._indexes:
self._collection.data[i]['visible'] = value
self._collection.redraw([])
class ShapeCollectionVisual(CompoundVisual):
# Shared multiprocessing pool
pool = None
def __init__(self, line_width=1, triangulation='gpc', layers=3, **kwargs):
"""
Represents collection of shapes to draw on VisPy scene
:param line_width: float
Width of lines/edges
:param triangulation: str
Triangulation method used for polygons translation
'vispy' - VisPy lib triangulation
'gpc' - Polygon2 lib
:param layers: int
Layers count
Each layer adds 2 visuals on VisPy scene. Be careful: more layers cause less fps
:param kwargs:
"""
self.data = {}
self.last_key = -1
self.lock = threading.Lock()
if not ShapeCollection.pool:
ShapeCollection.pool = Pool()
self.results = {}
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
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:
pass
m.set_gl_state(polygon_offset_fill=True, polygon_offset=(1, 1), cull_face=False)
for l in self._lines:
pass
l.set_gl_state(blend=True)
self.freeze()
def add(self, shape=None, color=None, face_color=None, visible=True, update=False, layer=1, tolerance=0.01):
"""
Adds shape to collection
:return:
:param shape: shapely.geometry
Shapely geometry object
:param color: str, tuple
Line/edge color
:param face_color: str, tuple
Polygon face color
:param visible: bool
Shape visibility
:param update: bool
Set True to redraw collection
:param layer: int
Layer number. 0 - lowest.
:param tolerance: float
Geometry simplifying tolerance
:return: int
Index of shape
"""
# Get new key
self.lock.acquire(True)
self.last_key += 1
key = self.last_key
self.lock.release()
# Prepare data for translation
self.data[key] = {'geometry': shape, 'color': color, 'face_color': face_color,
'visible': visible, 'layer': layer, 'tolerance': tolerance}
# Add data to process pool
self.results[key] = self.pool.apply_async(_update_shape_buffers, [self.data[key]])
if update:
self.redraw() # redraw() waits for pool process end
return key
def remove(self, key, update=False):
"""
Removes shape from collection
:param key: int
Shape index to remove
:param update:
Set True to redraw collection
"""
del self.results[key]
del self.data[key]
if update:
self.__update()
def clear(self, update=False):
"""
Removes all shapes from colleciton
:param update: bool
Set True to redraw collection
"""
self.data.clear()
if update:
self.__update()
def __update(self):
"""
Merges internal buffers, sets data to visuals, redraws collection on scene
"""
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'] and 'line_pts' in data:
try:
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 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 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)
.reshape((-1, 3)), face_colors=np.asarray(mesh_colors[i]))
else:
mesh.set_data()
mesh._bounds_changed()
# 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()
line._bounds_changed()
self._bounds_changed()
def redraw(self, indexes=None):
"""
Redraws collection
:param indexes: list
Shape indexes to get from process pool
"""
for i in self.data.keys() if not indexes else indexes:
try:
self.results[i].wait() # Wait for process results
if i in self.data:
self.data[i] = self.results[i].get() # Store translated data
except Exception as e:
print e
self.__update()
ShapeCollection = create_visual_node(ShapeCollectionVisual)