Source code for pyvsim.System

"""
.. module :: System
    :platform: Unix, Windows
    :synopsis: Base classes for making pyvsim work
    
This module contains the methods and classes used for interfacing "with the
System". Basically, the two functionalities of this module are:

* File handling
* Displaying
    
.. moduleauthor :: Ricardo Entz <maiko at thebigheads.net>

.. license::
    PyVSim v.1
    Copyright 2013 Ricardo Entz
    
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    
        http://www.apache.org/licenses/LICENSE-2.0
    
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
"""
import threading
import numpy as np
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
import matplotlib.pyplot as plt
import Core
import json
import re
import cPickle
try:
    import vtk
    VTK_PRESENT = True
except ImportError:
    VTK_PRESENT = False
    print "VTK not found"
    
VERSION = "1.0"

[docs]def plot(obj, mode="vtk", displayAxes = True): """ Convenience function to avoid the routine of creating visitor, making it visit the part tree and ask for plotting. *Warning* - Due to bugs both in Matplotlib and VTK, the program execution is paused until the window is closed. So plotting should be the last operation in the pipeline. Parameters ---------- obj : Primitives.Assembly The scenario to be displayed mode : "vtk" or "mpl" The library to be used in displaying the scenario, the standard option is "vtk", which is faster and supports rendering complex scenarios. Some scenarios, however, can be plotted using Matplotlib. displayAxes : boolean When plotting with VTK, the axis can be hidden by setting this option to false. """ plotter = Plotter(mode) obj.acceptVisitor(plotter) plotter.display(displayAxes)
[docs]def save(obj, filename=None, mode="pickle"): """ Convenience function for saving a Primitives.Assembly object. Parameters ---------- obj : Primitives.Assembly The scenario to be saved. filename : string The name of the output file mode : "pickle" or "json" Specifies the file format to be used in saving. The default option is pickle (which is fast and bug free), use JSON only if you need to edit the file in a human-readable way. """ if mode == "pickle": saver = Saver(mode = Saver.PICKLE) elif mode == "json": saver = Saver(mode = Saver.JSON) obj.acceptVisitor(saver) saver.dump(filename)
[docs]def load(filename, mode = None): """ Convenience function for loading a part tree. Pickle/JSON is chosen by trial and error Parameters ---------- filename : string The name of the input file mode : "pickle" or "json" Specifies the file format of the input file. The default option is None, which will make the loader first try to unpickle the file (which is fast and bug free). Use JSON only if you need to edit the file in a human-readable way. Raises ------ ValueError If decoding was not possible """ f = open(filename,'r') try: rawdata = cPickle.load(f) return rawdata except cPickle.UnpicklingError: print "Could not decode pickle, trying JSON" finally: f.close() f = open(filename,'r') try: rawdata = json.load(f, cls = pyvsimJSONDecoder) finally: f.close() return rawdata
[docs]def Plotter(mode="vtk"): """ This is a factory that returns a plotter visitor. It uses information about the Python installation to return a compatible plotter. Parameters ---------- mode : "vtk" or "mpl" "vtk" Uses the Python wrapping of the Visualization Toolkit (VTK) to plot the environment, whereas "mpl" uses Matplotlib to plot the environment (this option is not so efficient in plotting environments with complex objects) """ if mode == "vtk": if VTK_PRESENT: return VTKPlotter() else: print "Could not import VTK, returning a Matplotlib plotter" return PythonPlotter() elif mode == "mpl": return PythonPlotter() else: raise ValueError("Could not understand input " + mode) raise ImportError("Could not import a library for plotting. PyVSim" + "uses both Matplotlib and VTK")
[docs]class Visitor(object): """ This is the prototype of a class capable of traversing the parts tree while having access to all objects, one at a time. """ def __init__(self): pass def visit(self, obj): raise NotImplementedError
[docs]class VTKPlotter(Visitor): """ This class is a Facade to VTK. It takes a snapshot of the assembly tree and generates a VTK plot. """ def __init__(self): Visitor.__init__(self) self.actorList = [] def visit(self, obj): if obj.PLOTDIMS == -1: return None elif obj.PLOTDIMS == 0: self.actorList.append(self.pointsActor(obj)) elif obj.PLOTDIMS == 1: self.actorList.append(self.lineActor(obj)) elif obj.PLOTDIMS == 3: self.actorList.append(self.polyActor(obj)) else: raise ValueError("Attempted to plot a non pyvsim object")
[docs] def display(self,displayAxes=True): """ Creates a window and plots all the objects found during the last visit to the parts tree. I.e., before running this command, you should do:: main_assembly.acceptVisitor(plotter) Attention: This will stop the program execution until the window is closed. This is a "feature" of matplotlib and VTK. """ # print "There are %i elements to be plotted" % len(self.actorList) window = self.VTKWindow(displayAxes) window.addActors(self.actorList) window.start() return window
def pointsActor(self, obj): npts = np.size(obj.points, 0) vertices = vtk.vtkCellArray() ptsource = vtk.vtkPoints() ptsource.SetNumberOfPoints(npts) for n,p in enumerate(obj.points): ptsource.SetPoint(n,p) vertices.InsertNextCell(1) vertices.InsertCellPoint(n) point = vtk.vtkPolyData() point.SetPoints(ptsource) point.SetVerts(vertices) mapper = vtk.vtkPolyDataMapper() mapper.SetInput(point) actor = vtk.vtkLODActor() actor.SetMapper(mapper) actor.GetProperty().SetPointSize(1) return actor
[docs] def lineActor(self,obj): """ Returns an object of type vtkLODActor for rendering within a VTK pipeline """ me = vtk.vtkPolyData() pts = vtk.vtkPoints() cts = vtk.vtkCellArray() for n in range(len(obj.points)): pts.InsertPoint(n,obj.points[n][0], obj.points[n][1], obj.points[n][2]) for n in range(1,len(obj.points)): cts.InsertNextCell(2) cts.InsertCellPoint(n-1) cts.InsertCellPoint(n) me.SetPoints(pts) me.SetLines(cts) dataMapper = vtk.vtkPolyDataMapper() dataMapper.SetInput(me) dataActor =vtk.vtkLODActor() dataActor.SetMapper(dataMapper) if obj.color is not None: if np.size(obj.color) == 3: dataActor.GetProperty().SetColor(obj.color[0], obj.color[1], obj.color[2]) else: carray = vtk.vtkUnsignedCharArray() carray.SetNumberOfComponents(3) carray.SetName("Colors") color = (obj.color*255).astype(int) for c in color: carray.InsertNextTupleValue(c) me.GetCellData().SetScalars(carray) if obj.opacity is not None: dataActor.GetProperty().SetOpacity(obj.opacity) if obj.width is not None: dataActor.GetProperty().SetLineWidth(obj.width) return dataActor
[docs] def polyActor(self,obj): """ Returns an object of type vtkLODActor for rendering within a VTK pipeline """ actor = vtk.vtkPolyData() pts = vtk.vtkPoints() cts = vtk.vtkCellArray() for n in range(len(obj.points)): pts.InsertPoint(n,obj.points[n,0], obj.points[n,1], obj.points[n,2]) for n in range(len(obj.connectivity)): cts.InsertNextCell(3) for node in obj.connectivity[n]: cts.InsertCellPoint(node) actor.SetPoints(pts) actor.SetPolys(cts) # If the normals of the object are specified (smooth object), this is # rendered as such if obj.normals is not None: nrm = vtk.vtkDoubleArray() nrm.SetNumberOfComponents(3) nrm.SetNumberOfTuples(len(obj.points)) for n in range(len(obj.points)): nrm.SetTuple(n,obj.normals[n].tolist()) actor.GetPointData().SetNormals(nrm) dataMapper = vtk.vtkPolyDataMapper() dataMapper.SetInput(actor) dataActor =vtk.vtkLODActor() dataActor.SetMapper(dataMapper) if obj.color is not None: dataActor.GetProperty().SetColor(obj.color[0], obj.color[1], obj.color[2]) if obj.opacity is not None: dataActor.GetProperty().SetOpacity(obj.opacity) return dataActor
[docs] class VTKWindow(threading.Thread): """ A window to plot objects using VTK. Even though this stays in a separate thread, a error in VTK interface to Python makes it wait until the window is closed. """ def __init__(self, displayAxes = True): threading.Thread.__init__(self) self.footText = "PyVSim ver. " + VERSION self.windowTitle = "PyVSim Visualization window - powered by VTK" self.windowColor = [0,0.25,0.40] self.windowSize = [800,800] self.actorlist = None self.displayAxes = displayAxes self.rend = vtk.vtkRenderer() self.window = vtk.vtkRenderWindow() self.interactor = vtk.vtkRenderWindowInteractor() self.legend = vtk.vtkTextActor() self.rend.SetBackground(self.windowColor[0], self.windowColor[1], self.windowColor[2]) self.window.SetSize(self.windowSize[0],self.windowSize[0]) self.window.AddRenderer(self.rend) self.interactor.SetRenderWindow(self.window) self.legend.GetTextProperty().SetFontSize(12) self.legend.SetPosition2(0,0) self.legend.SetInput(self.footText) self.rend.AddActor(self.legend) def addActors(self,actorlist): self.actorlist = actorlist for actor in actorlist: self.rend.AddActor(actor) def run(self): if self.displayAxes: axesActor = vtk.vtkAxesActor() axesActor.SetShaftTypeToLine() axes = vtk.vtkOrientationMarkerWidget() axes.SetOrientationMarker(axesActor); axes.SetInteractor(self.interactor); axes.EnabledOn(); axes.InteractiveOn(); self.window.Render() self.window.SetWindowName(self.windowTitle) self.interactor.Start()
[docs]class PythonPlotter(Visitor): """ This visitor class creates a plot of the assembly tree using the Matplotlib library from python. """ def __init__(self): Visitor.__init__(self) self.fig = plt.figure() self.fig.canvas.set_window_title('PyVSim Visualization window ' + 'ver. ' + VERSION + ' - powered by Matplotlib') self.ax = self.fig.gca(projection='3d')
[docs] def visit(self, obj): """ Takes a snapshot of the object and creates a elements in a Matplotlib window. """ if obj.PLOTDIMS == -1: return None elif obj.PLOTDIMS == 0: self.pointsActor(obj) elif obj.PLOTDIMS == 1: self.lineActor(obj) elif obj.PLOTDIMS == 3: self.polyActor(obj) else: raise ValueError("Attempted to plot a non pyvsim object")
[docs] def display(self,displayAxes=True): """ Creates a window and plots all the objects found during the last visit to the parts tree. I.e., before running this command, you should do:: main_assembly.acceptVisitor(plotter) Attention: This will stop the program execution until the window is closed. This is a "feature" of matplotlib and VTK. """ _ = displayAxes plt.show()
def pointsActor(self, obj): self.ax.scatter3D(obj.points[:,0],obj.points[:,1],obj.points[:,2])
[docs] def lineActor(self,obj): """ Adds a collection to the current axes to draw a line """ col = Line3DCollection([obj.points]) if obj.color is not None: if obj.opacity is None: obj.opacity = 1 col.set_color([obj.color[0], obj.color[1], obj.color[2], obj.opacity]) self.ax.add_collection(col)
[docs] def polyActor(self,obj): """ Adds a collection to the current axes to draw a surface """ for n in range(np.size(obj.connectivity,0)): col = Poly3DCollection([obj.points[obj.connectivity]]) col = Poly3DCollection([obj.points[obj.connectivity[n]]]) if obj.color is None: color = [0.5,0.5,0.5] else: color = obj.color if obj.opacity is None: opacity = 0.3 else: opacity = obj.opacity col.set_color([color[0], color[1], color[2], opacity]) col.set_edgecolor([color[0], color[1], color[2], opacity]) # color mapping #col.set_array(val) #col.set_cmap(cm.hot) self.ax.add_collection(col)
[docs]class Saver(Visitor): """ This is the standard PyVSim saving routine, using python cPickle. The performance is quite good and does not require complicated parsing and conversion of the tree. However, the result is not human readable. Use when performance and reliability are desired. """ JSON = 1 PICKLE = 0 def __init__(self, mode = JSON): self.topickle = None self.mode = mode def visit(self, obj): if obj.parent is None: self.topickle = obj def dump(self, name = None): if self.mode == Saver.PICKLE: if name is None: cPickle.dumps(self.topickle) else: f = open(name,'w') try: cPickle.dump(self.topickle, f) finally: f.close() elif self.mode == Saver.JSON: if name == None: json.dumps(self.topickle, cls = pyvsimJSONEncoder, indent = 2) else: f = open(name,'w') try: json.dump(self.topickle, f, cls = pyvsimJSONEncoder, indent = 2) finally: f.close()
[docs]class pyvsimJSONEncoder(json.JSONEncoder): """ A JSON Encoder capable of dealing with a pyvsim simulation tree without creating duplicates of objects and solving circular references (at least the type found naturally in a pyvsim tree). This class is also aware of PyvsimObjects and numpy.ndarrays. """ FILEMODE = None SCREENMODE = 2 def __init__(self, skipkeys = False, ensure_ascii = True, check_circular = False, allow_nan = True, sort_keys = True, indent = None, separators = None, encoding = 'utf-8', default = None): json.JSONEncoder.__init__(self, skipkeys = False, ensure_ascii = True, check_circular = False, allow_nan = True, sort_keys = sort_keys, indent = indent, separators = None, encoding = 'utf-8', default = None) self.serializedObjects = {}
[docs] def default(self, obj): """ Objects are encoded in a special dictionary, with the magic key "object_type", which is essential for the unpacking of the object. It exploits the property __dict__ of the objects to pack the data """ if isinstance(obj, object): temp = {} temp["object_module"] = str(obj.__class__.__module__) temp["object_type"] = str(obj.__class__.__name__) temp["object_id"] = id(obj) if isinstance(obj, np.ndarray): temp["dtype"] = str(obj.dtype) temp["data"] = obj.tolist() return temp if self.serializedObjects.has_key(id(obj)): temp["object_dict"] = None else: try: temp["object_dict"] = obj.__getstate__() except AttributeError: temp["object_dict"] = obj.__dict__ self.serializedObjects[id(obj)] = temp return temp return json.JSONEncoder.default(self, obj)
[docs]class pyvsimJSONDecoder(json.JSONDecoder): """ Extension of the python JSONDecoder class aware of pyvsim objects (via the PyvsimObject interface) and numpy.ndarrays """ def __init__(self, encoding=None, object_hook=None, parse_float=None, parse_int=None, parse_constant=None, strict=True, object_pairs_hook=None): json.JSONDecoder.__init__(self, object_hook=self.default, strict = False) self.cornFlakes = {}
[docs] def default(self, obj): """ Objects are encoded in a special-purpose dictionary with the magic entry "object_type". When this key is found, an object of the type is instantiated and receives the data contained in the dictionary. Another problem is dealing with object copies. The variable cornFlakes stores a reference to each object that was already unpacked, and when the internal identifiers match, the pointer is reused. """ if (isinstance(obj, Core.PyvsimObject) or obj is None or isinstance(obj, np.ndarray)): return obj if obj.has_key("object_type"): if not self.cornFlakes.has_key(obj["object_id"]): if obj.has_key("dtype"): obj["data"] = self.treatArray(obj) myobject = obj["data"] else: p = re.split("[\.\']",obj["object_module"]) pkg = __import__(p[0]) mod = getattr(pkg,p[1]) myobject = getattr(mod,obj["object_type"])() self.cornFlakes[obj["object_id"]] = myobject try: if obj["object_dict"] is not None: self.cornFlakes[obj["object_id"]].__dict__ = obj["object_dict"] except KeyError: pass return self.cornFlakes[obj["object_id"]] return obj
[docs] def treatArray(self, obj): """ This is a tricky method when the numpy.ndarray contains objects (for example in the list of ray tracing intersections). """ if obj["dtype"] == "object": obj["data"] = np.array(obj["data"]) iterator = np.nditer(obj["data"], flags=['refs_ok','multi_index']) while not iterator.finished: obj["data"][iterator.multi_index] = \ self.default(obj["data"][iterator.multi_index]) iterator.iternext() return np.array(obj["data"])