import os
import json
import distutils
import distutils.dir_util
import webbrowser
from sympy.physics.mechanics import ReferenceFrame, Point
import pydy_viz
from .camera import PerspectiveCamera
from .server import Server
from .light import PointLight
__all__ = ['Scene']
try:
from IPython.lib import backgroundjobs as bg
except ImportError:
IPython = None
[docs]class Scene(object):
"""
Scene class holds all the data required for the visualizations/
animation of a system.
It has methods for inputting the numerical data from the numerical
integrations of Equations of Motions and convert them to JSON
values, which can be then parsed by Javascripts(webgls).
A scene object takes a ReferenceFrame, and a Point as required
arguments. The reference_frame and point act as the inertial
frame and origin with respect to which all objects are oriented
and rendered in the visualizations
A scene needs to be supplied with visualization_frames, Cameras,
and Light objects, as optional arguments.
A scene can also be supplied with the height and width of the
browser window where visualization would be displayed.
Default is 800 * 800.
"""
def __init__(self, reference_frame, origin, *visualization_frames,
**kwargs):
"""
Initializes a Scene instance.
It requires a reference frame and a point to be initialized.
Parameters
==========
reference_frame : ReferenceFrame
All the transformations would be carried out with respect
to this reference frame.
origin : Point
All the transformations would be carried out with respect
to this point.
visualization_frames : VisualizationFrame
a tuple of visualization frames which are to visualized in
the scene.
All the transformations would be carried out with respect to
this reference frame.
origin : Point
All the transformations would be carried out with respect to
this point.
visualization_frames : VisualizationFrame
A tuple of visualization frames which are to visualized in the
scene.
name : str, optional
Name of Scene object.
width : int or float, optional
width of the canvas used for visualizations.Default is 800.
height : int or float
height of the canvas used for visualizations.Default is 800.
camera : Camera, optional
camera with which to display the object. Default is
PerspectiveCamera, with reference_frame and origin same
as defined for this scene.
"""
self._reference_frame = reference_frame
self._origin = origin
self.visualization_frames = list(visualization_frames)
try:
self._name = kwargs['name']
except KeyError:
self._name = 'unnamed'
try:
self._width = kwargs['width']
except KeyError:
self._width = 800
try:
self._height = kwargs['height']
except KeyError:
self._height = 800
try:
self.cameras = kwargs['cameras']
except KeyError:
self.cameras = [PerspectiveCamera('DefaultCamera',
self._reference_frame,
self._origin.locatenew(
'p_camera',
10*self._reference_frame.z))]
try:
self.lights = kwargs['lights']
except KeyError:
self.lights = [PointLight('DefaultLight',
self._reference_frame,
self._origin.locatenew(
'p_light',
10*self._reference_frame.z))]
@property
def name(self):
"""
Returns Name of Scene.
"""
return self._name
@name.setter
[docs] def name(self, new_name):
"""
sets name of scene.
"""
if not isinstance(new_name, str):
raise TypeError('Name should be a valid str.')
else:
self._name = new_name
@property
def origin(self):
"""
returns Origin of the Scene.
"""
return self._origin
@origin.setter
[docs] def origin(self, new_origin):
"""
sets origin of the scene
"""
if not isinstance(new_origin, Point):
raise TypeError('''origin should be a valid Point Object''')
else:
self._origin = new_origin
@property
def reference_frame(self):
"""
returns reference_frame of the Scene.
"""
return self._reference_frame
@reference_frame.setter
[docs] def reference_frame(self, new_reference_frame):
"""
Sets reference frame for the scene.
"""
if not isinstance(new_reference_frame, ReferenceFrame):
raise TypeError('''reference_frame should be a valid
ReferenceFrame object.''')
else:
self._reference_frame = new_reference_frame
[docs] def generate_visualization_dict(self, dynamic_variables,
constant_variables, dynamic_values,
constant_values):
"""
generate_visualization_dict() method generates
a dictionary of visualization data
Parameters
==========
dynamic_variables : Sympifyable list or tuple
This contains all the dynamic symbols or state variables
which are required for solving the transformation matrices
of all the frames of the scene.
constant_variables : Sympifyable list or tuple
This contains all the symbols for the parameters which are
used for defining various objects in the system.
dynamic_values : list or tuple
initial states of the system. The list or tuple
should be respective to the state_sym.
constant_values : list or tuple
values of the parameters. The list or tuple
should be respective to the par_sym.
Returns
=======
The dictionary contains following keys:
1) Width of the scene.
2) Height of the scene.
3) name of the scene.
4) frames in the scene, which contains sub-dictionaries
of all the visualization frames information.
"""
self._scene_data = {}
self._scene_data['name'] = self._name
self._scene_data['height'] = self._height
self._scene_data['width'] = self._width
self._scene_data['frames'] = []
self._scene_data['cameras'] = []
self._scene_data['lights'] = []
for frame in self.visualization_frames:
frame.generate_transformation_matrix(self._reference_frame,
self._origin)
frame.generate_numeric_transform_function(dynamic_variables,
constant_variables)
frame.evaluate_transformation_matrix(
dynamic_values, constant_values)
self._scene_data['frames'].append(
frame.generate_visualization_dict())
for camera in self.cameras:
camera.generate_transformation_matrix(self._reference_frame,
self._origin)
camera.generate_numeric_transform_function(dynamic_variables,
constant_variables)
camera.evaluate_transformation_matrix(dynamic_values,
constant_values)
self._scene_data['cameras'].append(
camera.generate_visualization_dict()
)
for light in self.lights:
light.generate_transformation_matrix(self._reference_frame,
self._origin)
light.generate_numeric_transform_function(dynamic_variables,
constant_variables)
light.evaluate_transformation_matrix(dynamic_values,
constant_values)
self._scene_data['lights'].append(
light.generate_visualization_dict())
return self._scene_data
[docs] def generate_visualization_json(self, dynamic_variables,
constant_variables, dynamic_values,
constant_values, save_to='data.json'):
"""
generate_visualization_json() method generates a json str, which is
saved to file.
Parameters
==========
dynamic_variables : Sympifyable list or tuple
This contains all the dynamic symbols or state variables
which are required for solving the transformation matrices
of all the frames of the scene.
constant_variables : Sympifyable list or tuple
This contains all the symbols for the parameters which are
used for defining various objects in the system.
dynamic_values : list or tuple
initial states of the system. The list or tuple
should be respective to the state_sym.
constant_values : list or tuple
values of the parameters. The list or tuple
should be respective to the par_sym.
save_to : str
path to the file where to write the generated data JSON.
the path should be chosen such as to have the write
permissions to the user.
Returns
=======
The dictionary contains following keys:
1) Width of the scene.
2) Height of the scene.
3) name of the scene.
4) frames in the scene, which contains sub-dictionaries
of all the visualization frames information.
"""
self.saved_json_file = save_to
self._data_dict = self.generate_visualization_dict(dynamic_variables,
constant_variables,
dynamic_values,
constant_values)
outfile = open(self.saved_json_file, 'w')
outfile.write(json.dumps(self._data_dict, indent=4,
separators=(',', ': ')))
outfile.close()
def _copy_static_dir(self):
"""
Copies all the static files required in the
visualization to the current working directory
The files and sub directories are stored within
a hidden directory named .pydy_viz in the current
working directory.
Working directory can be cleaned by calling _cleanup()
method, which deletes the .pydy_viz directory.
"""
dst = os.path.join(os.getcwd(), '.pydy_viz')
os.mkdir(dst)
src = os.path.join(os.path.dirname(pydy_viz.__file__), 'static')
distutils.dir_util.copy_tree(src, dst)
def _cleanup(self):
distutils.dir_util.remove_tree(os.path.join(os.getcwd(), '.pydy_viz'))
def _display_from_interpreter(self):
server = Server(json=self.saved_json_file)
print '''Your visualization is being rendered at
http://localhost:%s/
Visit the url in your webgl compatible browser
to see the animation in full glory''' % (server.port)
server.run()
def _display_from_ipython(self):
# This is a hack using IPython BackgroundJobs
# module. Once we have an IPython2.0 release
# It can be modified to display visualizations
# in IPython output cell itself.
server = Server(json=self.saved_json_file)
jobs = bg.BackgroundJobManager()
jobs.new('server.run()')
print '''
Your visualization is being rendered at
http://localhost:%s/
Opening the visualization in new tab...'''%(server.port)
webbrowser.open("http://localhost:%s/"%server.port)
[docs] def display(self):
"""
display method can be used in two ways.
When called from IPython notebook, it shows the visualization
in the form of output cell in the IPython notebook.
If it is called from python interpreter or
IPython interpreter(not notebook), It generates an html file,
in the current directory, which can be opened in the webgl
compliant browser for viewing the visualizations.
The simulation data is used from this scene, hence
all simulation data generation methods should be called before
calling this method
"""
try:
# If it detects any IPython frontend
# (qtconsole, interpreter or notebook)
config = get_ipython().config
self._display_from_ipython()
except:
self._display_from_interpreter()