From e6d7ea653358dd9162768dc7e90663c4b8add3fe Mon Sep 17 00:00:00 2001 From: "Jason K. Moore" Date: Sat, 2 Jan 2016 03:30:50 -0800 Subject: [PATCH 1/3] Improvements to the camera code. - Utilized inheritance from VisualizationFrame. - Added more tests. - Improved doc strings. - Fixed getters/setters. --- pydy/viz/camera.py | 402 ++++++------------ pydy/viz/tests/test_scene.py | 3 + .../tests/test_visualization_frame_scene.py | 394 ++++++++++------- pydy/viz/visualization_frame.py | 35 +- 4 files changed, 394 insertions(+), 440 deletions(-) diff --git a/pydy/viz/camera.py b/pydy/viz/camera.py index 845447c9..96d0e11a 100644 --- a/pydy/viz/camera.py +++ b/pydy/viz/camera.py @@ -1,7 +1,11 @@ +#!/usr/bin/env python + +# standard lib import warnings -from sympy.matrices.expressions import Identity +# local from ..utils import PyDyUserWarning +from .shapes import Shape from .visualization_frame import VisualizationFrame __all__ = ['PerspectiveCamera', 'OrthoGraphicCamera'] @@ -10,173 +14,120 @@ class PerspectiveCamera(VisualizationFrame): - """Creates a Perspective Camera for visualization. The camera is - inherited from VisualizationFrame. It can be attached to dynamics - objects, hence we can get a moving camera. All the transformation matrix - generation methods are applicable to a Perspective Camera. - - Like VisualizationFrame, it can also be initialized using: - - 1. Rigidbody - 2. ReferenceFrame, Point - 3. ReferenceFrame, Particle - - Either one of these must be supplied during initialization. Unlike - VisualizationFrame, it does not require a Shape argument. - - Parameters - ========== - name : str - A name for the PerspectiveCamera(optional). Default is 'unnamed' - fov : float, default=45.0 - Field Of View, It determines the angle between the top and bottom of - the viewable area (in degrees). - near : float - The distance of near plane of the PerspectiveCamera. All objects - closer to this distance are not displayed. - far : int or float - The distance of far plane of the PerspectiveCamera. All objects - farther than this distance are not displayed. + """Creates a perspective camera for use in a scene. The camera is inherited + from ``VisualizationFrame``, and thus behaves similarly. It can be attached + to dynamics objects, hence we can get a moving camera. All the + transformation matrix generation methods are applicable to a + ``PerspectiveCamera``. """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, fov=45.0, near=1.0, far=1000.0): """Initialises a PerspectiveCamera object. To initialize a - visualization frame, one needs to supply a name (optional), a - reference frame, a point, field of view (fov) (optional), near plane - distance (optional) and far plane distance (optional). + PerspectiveCamera, one needs to supply a name (optional), a reference + frame, a point, field of view (fov) (optional), near plane distance + (optional) and far plane distance (optional). + + Like ``VisualizationFrame``, it can also be initialized using one of + these three argument sequences: + + Rigidbody + ``PerspectiveCamera(rigid_body)`` + ReferenceFrame, Point + ``PerspectiveCamera(ref_frame, point)`` + ReferenceFrame, Particle + ``PerspectiveCamera(ref_frame, particle)`` + + Note that you can also supply and optional name as the first positional + argument, e.g.:: + + ``PerspectiveCamera('camera_name', rigid_body)`` + + Additional optional keyword arguments are below: + + Parameters + ========== + fov : float, default=45.0 + Field Of View, It determines the angle between the top and bottom + of the viewable area (in degrees). + near : float + The distance of near plane of the PerspectiveCamera. All objects + closer to this distance are not displayed. + far : int or float + The distance of far plane of the PerspectiveCamera. All objects + farther than this distance are not displayed. Examples ======== - >>> from pydy.viz import VisualizationFrame, Shape + + >>> from sympy import symbols >>> from sympy.physics.mechanics import (ReferenceFrame, Point, ... RigidBody, Particle, ... inertia) - >>> from sympy import symbols + >>> from pydy.viz import PerspectiveCamera >>> I = ReferenceFrame('I') >>> O = Point('O') - >>> shape = Shape() + >>> # initializing with reference frame, point >>> camera1 = PerspectiveCamera('frame1', I, O) + + >>> # Initializing with a RigidBody >>> Ixx, Iyy, Izz, mass = symbols('Ixx Iyy Izz mass') >>> i = inertia(I, Ixx, Iyy, Izz) >>> rbody = RigidBody('rbody', O, I, mass, (inertia, O)) - >>> # Initializing with a rigidbody .. >>> camera2 = PerspectiveCamera('frame2', rbody) + + >>> # initializing with Particle, reference_frame >>> Pa = Particle('Pa', O, mass) - >>> # initializing with Particle, reference_frame ... >>> camera3 = PerspectiveCamera('frame3', I, Pa) + """ - msg = ("Rotation of Perspective Camera does not work " - "properly in the visualiser.") - warnings.warn(msg, PyDyUserWarning) - - try: - self._fov = kwargs['fov'] - except KeyError: - self._fov = 45.0 - - try: - self._near = kwargs['near'] - except KeyError: - self._near = 1.0 - - try: - self._far = kwargs['far'] - except KeyError: - self._far = 1000.0 - - # Now we use same approach as in VisualizationFrame for setting - # reference_frame and origin - i = 0 - # If first arg is not str, name the visualization frame 'unnamed' - if isinstance(args[i], str): - self._name = args[i] - i += 1 - else: - self._name = 'unnamed' - - try: - self._reference_frame = args[i].get_frame() - self._origin = args[i].masscenter - - except AttributeError: - # It is not a rigidbody, hence this arg should be a reference - # frame - try: - # TODO : dcm is never used. - dcm = args[i]._dcm_dict - self._reference_frame = args[i] - i += 1 - except AttributeError: - raise TypeError('A ReferenceFrame is to be supplied ' - 'before a Particle/Point.') - - # Now next arg can either be a Particle or point - try: - self._origin = args[i].point - except AttributeError: - self._origin = args[i] - - # basic thing required, transform matrix - self._transform = Identity(4).as_mutable() + # NOTE: This allows us to use inhertiance even though cameras don't + # need a shape. In the future, this could be a camera shape that could + # be made visible in the scene (only important for multiple cameras). + args = list(args) + [Shape()] + + super(PerspectiveCamera, self).__init__(*args) + + self.fov = fov + self.near = near + self.far = far def __str__(self): - return 'PerspectiveCamera: ' + self._name + return 'PerspectiveCamera: ' + self.name def __repr__(self): return 'PerspectiveCamera' @property def fov(self): - """ - attribute for Field Of view of a PerspectiveCamera. - Default value is 45 degrees - """ return self._fov @fov.setter def fov(self, new_fov): - if not isinstance(new_fov, (int, str)): - raise TypeError('fov should be supplied in int or float') - else: - self._fov = new_fov + self._fov = float(new_fov) @property def near(self): - """ - attribute for Near Plane distance of a PerspectiveCamera. - Default value is 1 - """ return self._near @near.setter def near(self, new_near): - if not isinstance(new_near, (int, str)): - raise TypeError('near should be supplied in int or float') - else: - self._near = new_near + self._near = float(new_near) @property def far(self): - """ - Attribute for Far Plane distance of a PerspectiveCamera. The default - value is ``1000.0``. - """ return self._far @far.setter def far(self, new_far): - if not isinstance(new_far, (int, str)): - raise TypeError('far should be supplied in int or float') - else: - self._far = new_far + self._far = float(new_far) - def generate_scene_dict(self): - """This method generates information for a static visualization in - the initial conditions, in the form of dictionary. This contains - camera parameters followed by an init_orientation Key. + def generate_scene_dict(self, **kwargs): + """This method generates information for a static visualization in the + initial conditions, in the form of dictionary. This contains camera + parameters followed by an init_orientation key. Before calling this method, all the transformation matrix generation methods should be called, or it will give an error. @@ -192,174 +143,115 @@ def generate_scene_dict(self): 5. init_orientation: Initial orientation of the camera """ - scene_dict = {id(self): {}} - scene_dict[id(self)]['name'] = self.name - scene_dict[id(self)]['type'] = self.__repr__() + scene_dict = super(PerspectiveCamera, self).generate_scene_dict(**kwargs) + scene_dict[id(self)]['type'] = self.__class__.__name__ scene_dict[id(self)]['fov'] = self.fov scene_dict[id(self)]['near'] = self.near scene_dict[id(self)]['far'] = self.far - scene_dict[id(self)]["simulation_id"] = id(self) - scene_dict[id(self)]["init_orientation"] = self._visualization_matrix[0] return scene_dict - def generate_simulation_dict(self): - """Generates the simulation information for this visualization - frame. It maps the simulation data information to the scene - information via a unique id. - Before calling this method, all the transformation matrix generation - methods should be called, or it will give an error. +class OrthoGraphicCamera(VisualizationFrame): + """Creates a orthographic camera for use in a scene. The camera is + inherited from ``VisualizationFrame``, and thus behaves similarly. It can + be attached to dynamics objects, hence we can get a moving camera. All the + transformation matrix generation methods are applicable to a + ``OrthoGraphicCameraCamera``. - Returns - ======= - simulation_dict : dictionary - A dictionary containing list of 4x4 matrices mapped to the - unique id as the key. - """ - simulation_dict = {} - try: - simulation_dict[id(self)] = self._visualization_matrix + """ - except: - raise RuntimeError("Please call the numerical " - "transformation methods, " - "before generating visualization dict.") + def __init__(self, *args, near=1.0, far=1000.0): + """Initialises a OrthoGraphicCameraCamera object. To initialize a + OrthoGraphicCameraCamera, one needs to supply a name (optional), a + reference frame, a point, field of view (fov) (optional), near plane + distance (optional) and far plane distance (optional). - return simulation_dict + Like ``VisualizationFrame``, it can also be initialized using one of + these three argument sequences: + Rigidbody + ``OrthoGraphicCameraCamera(rigid_body)`` + ReferenceFrame, Point + ``OrthoGraphicCameraCamera(ref_frame, point)`` + ReferenceFrame, Particle + ``OrthoGraphicCameraCamera(ref_frame, particle)`` -class OrthoGraphicCamera(VisualizationFrame): - """Creates a OrthoGraphic Camera for visualization. The camera is - inherited from ``VisualizationFrame``. It can be attached to dynamics - objects, hence we can get a moving camera. All the transformation matrix - generation methods are applicable to a Perspective Camera. - - Like VisualizationFrame, it can also be initialized using: - - 1. :role:`Rigidbody` - 2. ReferenceFrame, Point - 3. ReferenceFrame, Particle - - Either one of these must be supplied during initialization. Unlike - VisualizationFrame, it doesnt require a Shape argument. - - Parameters - ========== - name : str, optional, default='unnamed' - A name for the PerspectiveCamera. - near : float, optional, default= - The distance of near plane of the PerspectiveCamera. All objects - closer to this distance are not displayed. - far : float, optional, default= - The distance of far plane of the PerspectiveCamera. All objects - farther than this distance are not displayed. + Note that you can also supply and optional name as the first positional + argument, e.g.:: - """ + OrthoGraphicCameraCamera('camera_name', rigid_body) - def __init__(self, *args, **kwargs): - """ - Initialises an OrthoGraphicCamera object. To initialize a - visualization frame, we need to supply a name (optional), a - reference frame, a point, near plane distance (optional) and far - plane distance (optional). + Additional optional keyword arguments are below: + + Parameters + ========== + near : float + The distance of near plane of the OrthoGraphicCameraCamera. All + objects closer to this distance are not displayed. + far : int or float + The distance of far plane of the OrthoGraphicCameraCamera. All + objects farther than this distance are not displayed. Examples ======== - >>> from pydy.viz import OrthoGraphicCamera + + >>> from sympy import symbols >>> from sympy.physics.mechanics import (ReferenceFrame, Point, ... RigidBody, Particle, ... inertia) - >>> from sympy import symbols + >>> from pydy.viz import OrthoGraphicCameraCamera >>> I = ReferenceFrame('I') >>> O = Point('O') - >>> shape = Shape() - >>> # Initializing with ReferenceFrame, Point - >>> camera1 = OrthoGraphicCamera('frame1', I, O) + + >>> # initializing with reference frame, point + >>> camera1 = OrthoGraphicCameraCamera('frame1', I, O) + + >>> # Initializing with a RigidBody >>> Ixx, Iyy, Izz, mass = symbols('Ixx Iyy Izz mass') >>> i = inertia(I, Ixx, Iyy, Izz) >>> rbody = RigidBody('rbody', O, I, mass, (inertia, O)) - >>> # Initializing with a Rigidbody - >>> camera2 = OrthoGraphicCamera('frame2', rbody) + >>> camera2 = OrthoGraphicCameraCamera('frame2', rbody) + + >>> # initializing with Particle, reference_frame >>> Pa = Particle('Pa', O, mass) - >>> # Initializing with Particle, ReferenceFrame - >>> camera3 = OrthoGraphicCamera('frame3', I, Pa) + >>> camera3 = OrthoGraphicCameraCamera('frame3', I, Pa) + """ - try: - self._near = kwargs['near'] - except KeyError: - self._near = 1 - - try: - self._far = kwargs['far'] - except KeyError: - self._far = 1000 - - # Now we use same approach as in VisualizationFrame for setting - # reference_frame and origin - i = 0 - # If first arg is not str, name the visualization frame 'unnamed' - if isinstance(args[i], str): - self._name = args[i] - i += 1 - else: - self._name = 'unnamed' - - try: - self._reference_frame = args[i].get_frame() - self._origin = args[i].masscenter - - except AttributeError: - # It is not a rigidbody, hence this arg should be a reference - # frame. - self._reference_frame = args[i] - i += 1 - - # Now next arg can either be a Particle or point - try: - self._origin = args[i].point - except AttributeError: - self._origin = args[i] - - # basic thing required, transform matrix - self._transform = Identity(4).as_mutable() + # NOTE: This allows us to use inhertiance even though cameras don't + # need a shape. In the future, this could be a camera shape that could + # be made visible in the scene (only important for multiple cameras). + args = list(args) + [Shape()] + + super(OrthoGraphicCamera, self).__init__(*args) + + self.near = near + self.far = far def __str__(self): - return 'OrthoGraphicCamera: ' + self._name + return self.__class__.__name__ + ': ' + self.name def __repr__(self): - return 'OrthoGraphicCamera' + return self.__class__.__name__ @property def near(self): - """Attribute for Near Plane distance of an OrthoGraphicCamera. - Default value is 1.0 - """ return self._near @near.setter def near(self, new_near): - if not isinstance(new_near, (int, str)): - raise TypeError('near should be supplied in int or float') - else: - self._near = new_near + self._near = float(new_near) @property def far(self): - """Attribute for Far Plane distance of an OrthoGraphicCamera. - Default value is 1000.0. - """ return self._far @far.setter def far(self, new_far): - if not isinstance(new_far, (int, str)): - raise TypeError('far should be supplied in int or float') - else: - self._far = new_far + self._far = float(new_far) - def generate_scene_dict(self): + def generate_scene_dict(self, **kwargs): """ This method generates information for a static visualization in the initial conditions, in the form of dictionary. This contains camera @@ -376,35 +268,9 @@ def generate_scene_dict(self): 4. init_orientation: Initial orientation of the camera """ - scene_dict = {id(self): {}} - scene_dict[id(self)]['name'] = self.name - scene_dict[id(self)]['type'] = self.__repr__() + scene_dict = super(OrthoGraphicCamera, self).generate_scene_dict(**kwargs) + scene_dict[id(self)]['type'] = self.__class__.__name__ scene_dict[id(self)]['near'] = self.near scene_dict[id(self)]['far'] = self.far - scene_dict[id(self)]["simulation_id"] = id(self) - scene_dict[id(self)]["init_orientation"] = self._visualization_matrix[0] return scene_dict - - def generate_simulation_dict(self): - """Generates the simulation information for this visualization - frame. It maps the simulation data information to the scene - information via a unique id. - - Returns - ======= - - A dictionary containing list of 4x4 matrices mapped to the unique id - as the key. - - """ - simulation_dict = {} - try: - simulation_dict[id(self)] = self._visualization_matrix - - except: - raise RuntimeError("Please call the numerical ", - "transformation methods, ", - "before generating visualization dict") - - return simulation_dict diff --git a/pydy/viz/tests/test_scene.py b/pydy/viz/tests/test_scene.py index 24f0b99d..cbb0abae 100644 --- a/pydy/viz/tests/test_scene.py +++ b/pydy/viz/tests/test_scene.py @@ -219,6 +219,9 @@ def test_generate_scene_dict(self): 'far': 1000.0, 'simulation_id': camera_id, 'near': 1.0, + 'color': 'grey', + 'material': 'default', + 'reference_frame_name': 'N', 'init_orientation': [1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, diff --git a/pydy/viz/tests/test_visualization_frame_scene.py b/pydy/viz/tests/test_visualization_frame_scene.py index 1f963b17..fd674b20 100644 --- a/pydy/viz/tests/test_visualization_frame_scene.py +++ b/pydy/viz/tests/test_visualization_frame_scene.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # external -from sympy import sin, cos, symbols +from sympy import sin, cos, symbols, pi from sympy.physics.mechanics import (dynamicsymbols, ReferenceFrame, Point, RigidBody, Particle, inertia) from numpy import radians @@ -18,22 +18,31 @@ class TestVisualizationFrameScene(object): def __init__(self): - #We define some quantities required for tests here.. - self.p = dynamicsymbols('p:3') - self.q = dynamicsymbols('q:3') - self.dynamic = list(self.p) + list(self.q) - self.states = [radians(45) for x in self.p] + \ - [radians(30) for x in self.q] - - self.I = ReferenceFrame('I') - self.A = self.I.orientnew('A', 'space', self.p, 'XYZ') - self.B = self.A.orientnew('B', 'space', self.q, 'XYZ') - - self.O = Point('O') - self.P1 = self.O.locatenew('P1', 10 * self.I.x + \ - 10 * self.I.y + 10 * self.I.z) - self.P2 = self.P1.locatenew('P2', 10 * self.I.x + \ - 10 * self.I.y + 10 * self.I.z) + + self.coords = dynamicsymbols('q:3') + self.speeds = dynamicsymbols('p:3') + + self.dynamic = list(self.coords) + list(self.speeds) + self.states = ([radians(45) for x in self.coords] + + [radians(30) for x in self.speeds]) + + self.inertial_ref_frame = ReferenceFrame('I') + self.A = self.inertial_ref_frame.orientnew('A', 'space', + self.coords, 'XYZ') + self.B = self.A.orientnew('B', 'space', self.speeds, 'XYZ') + self.camera_ref_frame = \ + self.inertial_ref_frame.orientnew('C', 'space', (pi / 2, 0, 0), + 'XYZ') + + self.origin = Point('O') + self.P1 = self.origin.locatenew('P1', + 10 * self.inertial_ref_frame.x + + 10 * self.inertial_ref_frame.y + + 10 * self.inertial_ref_frame.z) + self.P2 = self.P1.locatenew('P2', + 10 * self.inertial_ref_frame.x + + 10 * self.inertial_ref_frame.y + + 10 * self.inertial_ref_frame.z) self.point_list1 = [[2, 3, 1], [4, 6, 2], [5, 3, 1], [5, 3, 6]] self.point_list2 = [[3, 1, 4], [3, 8, 2], [2, 1, 6], [2, 1, 1]] @@ -41,51 +50,48 @@ def __init__(self): self.shape1 = Cylinder(1.0, 1.0) self.shape2 = Cylinder(1.0, 1.0) - - self.Ixx, self.Iyy, self.Izz = symbols('Ixx Iyy Izz') - self.mass = symbols('mass') + self.Ixx, self.Iyy, self.Izz = symbols('Ixx, Iyy, Izz') + self.mass = symbols('m') self.parameters = [self.Ixx, self.Iyy, self.Izz, self.mass] - self.param_vals = [0, 0, 0, 0] - + self.param_vals = [2.0, 3.0, 4.0, 5.0] self.inertia = inertia(self.A, self.Ixx, self.Iyy, self.Izz) - self.rigid_body = RigidBody('rigid_body1', self.P1, self.A, \ - self.mass, (self.inertia, self.P1)) + self.rigid_body = RigidBody('rigid_body1', self.P1, self.A, + self.mass, (self.inertia, self.P1)) - self.global_frame1 = VisualizationFrame('global_frame1', \ - self.A, self.P1, self.shape1) + self.global_frame1 = VisualizationFrame('global_frame1', self.A, + self.P1, self.shape1) - self.global_frame2 = VisualizationFrame('global_frame2', \ - self.B, self.P2, self.shape2) + self.global_frame2 = VisualizationFrame('global_frame2', self.B, + self.P2, self.shape2) - self.scene1 = Scene(self.I, self.O, \ - (self.global_frame1, self.global_frame2), \ - name='scene') + self.scene1 = Scene(self.inertial_ref_frame, self.origin, + (self.global_frame1, self.global_frame2), + name='scene') self.particle = Particle('particle1', self.P1, self.mass) - #To make it more readable - p = self.p - q = self.q - #Here is the dragon .. - self.transformation_matrix = \ - [[cos(p[1])*cos(p[2]), sin(p[2])*cos(p[1]), -sin(p[1]), 0], \ - [sin(p[0])*sin(p[1])*cos(p[2]) - sin(p[2])*cos(p[0]), \ - sin(p[0])*sin(p[1])*sin(p[2]) + cos(p[0])*cos(p[2]), \ - sin(p[0])*cos(p[1]), 0], \ - [sin(p[0])*sin(p[2]) + sin(p[1])*cos(p[0])*cos(p[2]), \ - -sin(p[0])*cos(p[2]) + sin(p[1])*sin(p[2])*cos(p[0]), \ - cos(p[0])*cos(p[1]), 0], \ - [10, 10, 10, 1]] - + q = self.coords + c = cos + s = sin + # Here is the dragon .. + self.transformation_matrix = [ + [c(q[1])*c(q[2]), s(q[2])*c(q[1]), -s(q[1]), 0], + [s(q[0])*s(q[1])*c(q[2]) - s(q[2])*c(q[0]), + s(q[0])*s(q[1])*s(q[2]) + c(q[0])*c(q[2]), + s(q[0])*c(q[1]), 0], + [s(q[0])*s(q[2]) + s(q[1])*c(q[0])*c(q[2]), + -s(q[0])*c(q[2]) + s(q[1])*s(q[2])*c(q[0]), + c(q[0])*c(q[1]), 0], + [10, 10, 10, 1]] def test_vframe_with_rframe(self): - self.frame1 = VisualizationFrame('frame1', self.I, self.O, \ - self.shape1) + self.frame1 = VisualizationFrame('frame1', self.inertial_ref_frame, + self.origin, self.shape1) assert self.frame1.name == 'frame1' - assert self.frame1.reference_frame == self.I - assert self.frame1.origin == self.O + assert self.frame1.reference_frame == self.inertial_ref_frame + assert self.frame1.origin == self.origin assert self.frame1.shape is self.shape1 self.frame1.name = 'frame1_' @@ -100,13 +106,13 @@ def test_vframe_with_rframe(self): self.frame1.shape = self.shape2 assert self.frame1.shape is self.shape2 - assert self.frame1.generate_transformation_matrix(self.I, self.O).tolist() == \ - self.transformation_matrix - + tr_mat = self.frame1.generate_transformation_matrix( + self.inertial_ref_frame, self.origin).tolist() + assert tr_mat == self.transformation_matrix def test_vframe_with_rbody(self): - self.frame2 = VisualizationFrame('frame2', self.rigid_body, \ - self.shape1) + self.frame2 = VisualizationFrame('frame2', self.rigid_body, + self.shape1) assert self.frame2.name == 'frame2' assert self.frame2.reference_frame == self.A @@ -127,16 +133,14 @@ def test_vframe_with_rbody(self): self.frame2.reference_frame = self.A self.frame2.origin = self.P1 - assert self.frame2.generate_transformation_matrix(self.I, self.O).tolist() == \ - self.transformation_matrix - - + tr_mat = self.frame2.generate_transformation_matrix( + self.inertial_ref_frame, self.origin).tolist() + assert tr_mat == self.transformation_matrix def test_vframe_with_particle(self): - self.frame3 = VisualizationFrame('frame3', \ - self.A, self.particle, \ - self.shape1) + self.frame3 = VisualizationFrame('frame3', self.A, self.particle, + self.shape1) assert self.frame3.name == 'frame3' assert self.frame3.reference_frame == self.A @@ -157,68 +161,86 @@ def test_vframe_with_particle(self): self.frame3.reference_frame = self.A self.frame3.origin = self.P1 - assert self.frame3.generate_transformation_matrix(self.I, self.O).tolist() == \ - self.transformation_matrix + tr_mat = self.frame3.generate_transformation_matrix( + self.inertial_ref_frame, self.origin).tolist() + assert tr_mat == self.transformation_matrix def test_vframe_without_name(self): - self.frame4 = VisualizationFrame(self.I, self.O, \ - self.shape1) + + self.frame4 = VisualizationFrame(self.inertial_ref_frame, self.origin, + self.shape1) assert self.frame4.name == 'unnamed' - #To check if referenceframe and origin are defined - #properly without name arg - assert self.frame4.reference_frame == self.I - assert self.frame4.origin == self.O + # To check if referenceframe and origin are defined properly without + # name arg + assert self.frame4.reference_frame == self.inertial_ref_frame + assert self.frame4.origin == self.origin assert self.frame4.shape is self.shape1 self.frame4.name = 'frame1_' assert self.frame4.name == 'frame1_' def test_numeric_transform(self): - self.list1 = [[0.5000000000000001, 0.5, \ - -0.7071067811865475, 0.0, \ - -0.14644660940672627, 0.8535533905932737, \ - 0.5, 0.0, \ - 0.8535533905932737, -0.14644660940672627, \ - 0.5000000000000001, 0.0, \ - 10.0, 10.0, 10.0, 1.0]] - - - self.list2 = [[-0.11518993731879767, 0.8178227645734215, \ - -0.563823734943801, 0.0, \ - 0.1332055011661179, 0.5751927992738988, \ - 0.8070994598700584, 0.0, \ - 0.984371663956036, 0.017865313009926137, \ - -0.17519491371464685, 0.0, \ - 20.0, 20.0, 20.0, 1.0]] - - - - self.global_frame1.generate_transformation_matrix(self.I, self.O) - self.global_frame1.generate_numeric_transform_function(self.dynamic, \ - self.parameters) - - assert_allclose(self.global_frame1.\ - evaluate_transformation_matrix(self.states, \ - self.param_vals), self.list1) - - self.global_frame2.generate_transformation_matrix(self.I, self.O) - self.global_frame2.generate_numeric_transform_function(self.dynamic, \ - self.parameters) - - assert_allclose(self.global_frame2.\ - evaluate_transformation_matrix(self.states, \ - self.param_vals), self.list2) + self.list1 = [[0.5000000000000001, + 0.5, + -0.7071067811865475, + 0.0, + -0.14644660940672627, + 0.8535533905932737, + 0.5, + 0.0, + 0.8535533905932737, + -0.14644660940672627, + 0.5000000000000001, + 0.0, + 10.0, + 10.0, + 10.0, + 1.0]] + + self.list2 = [[-0.11518993731879767, + 0.8178227645734215, + -0.563823734943801, + 0.0, + 0.1332055011661179, + 0.5751927992738988, + 0.8070994598700584, + 0.0, + 0.984371663956036, + 0.017865313009926137, + -0.17519491371464685, + 0.0, + 20.0, + 20.0, + 20.0, + 1.0]] + + + self.global_frame1.generate_transformation_matrix( + self.inertial_ref_frame, self.origin) + self.global_frame1.generate_numeric_transform_function( + self.dynamic, self.parameters) + + assert_allclose(self.global_frame1.evaluate_transformation_matrix( + self.states, self.param_vals), self.list1) + + self.global_frame2.generate_transformation_matrix( + self.inertial_ref_frame, self.origin) + self.global_frame2.generate_numeric_transform_function( + self.dynamic, self.parameters) + + assert_allclose(self.global_frame2.evaluate_transformation_matrix( + self.states, self.param_vals), self.list2) def test_perspective_camera(self): - #Camera is a subclass of VisualizationFrame, but without any - #specific shape attached. We supply only ReferenceFrame,Point - #to camera. and it inherits methods from VisualizationFrame - #Testing with rigid-body .. - camera = PerspectiveCamera('camera', self.rigid_body, fov=45, \ - near=1, far=1000) + # Camera is a subclass of VisualizationFrame, but without any specific + # shape attached. We supply only ReferenceFrame,Point to camera. and it + # inherits methods from VisualizationFrame + + camera = PerspectiveCamera('camera', self.rigid_body, fov=45, near=1, + far=1000) assert camera.name == 'camera' assert camera.reference_frame == self.A @@ -227,24 +249,22 @@ def test_perspective_camera(self): assert camera.near == 1 assert camera.far == 1000 - #Testing with reference_frame, particle .. - camera = PerspectiveCamera('camera', self.I, self.particle, \ - fov=45, near=1, far=1000) + camera = PerspectiveCamera('camera', self.inertial_ref_frame, + self.particle, fov=45, near=1, far=1000) assert camera.name == 'camera' - assert camera.reference_frame == self.I + assert camera.reference_frame == self.inertial_ref_frame assert camera.origin == self.P1 assert camera.fov == 45 assert camera.near == 1 assert camera.far == 1000 - #Testing with reference_frame, point .. - camera = PerspectiveCamera('camera', self.I, self.O, \ - fov=45, near=1, far=1000) + camera = PerspectiveCamera('camera', self.inertial_ref_frame, + self.origin, fov=45, near=1, far=1000) assert camera.name == 'camera' - assert camera.reference_frame == self.I - assert camera.origin == self.O + assert camera.reference_frame == self.inertial_ref_frame + assert camera.origin == self.origin assert camera.fov == 45 assert camera.near == 1 assert camera.far == 1000 @@ -269,23 +289,50 @@ def test_perspective_camera(self): camera.far = 500 assert camera.far == 500 - #Test unnamed - camera1 = PerspectiveCamera(self.I, self.O) + camera = PerspectiveCamera('camera', self.camera_ref_frame, + self.origin, fov=45, near=1, far=1000) + camera.generate_transformation_matrix(self.inertial_ref_frame, + self.origin) + camera.generate_numeric_transform_function(self.dynamic, + self.parameters) + viz_matrix = camera.evaluate_transformation_matrix(self.states, + self.param_vals) + expected_viz_matrix = [[1., 0., 0., 0., + 0., 0., 1., 0., + 0., -1., 0., 0., + 0., 0., 0., 1.]] + assert_allclose(viz_matrix, expected_viz_matrix) + + scene_dict = camera.generate_scene_dict() + + assert scene_dict[id(camera)]['type'] == 'PerspectiveCamera' + assert scene_dict[id(camera)]['fov'] == 45 + assert scene_dict[id(camera)]['near'] == 1 + assert scene_dict[id(camera)]['far'] == 1000 + assert scene_dict[id(camera)]['name'] == 'camera' + assert scene_dict[id(camera)]['init_orientation'] == \ + [1., 0., 0., 0., + 0., 0., 1., 0., + 0., -1., 0., 0., + 0., 0., 0., 1.] + + camera1 = PerspectiveCamera(self.inertial_ref_frame, self.origin) assert camera1.name == 'unnamed' - assert camera1.reference_frame == self.I - assert camera1.origin == self.O + assert camera1.reference_frame == self.inertial_ref_frame + assert camera1.origin == self.origin assert camera1.fov == 45 assert camera1.near == 1 assert camera1.far == 1000 def test_orthographic_camera(self): - #As compared to Perspective Camera, Orthographic Camera doesnt - #have fov, instead the left,right,top and bottom faces are - #adjusted by the Scene width, and height - #Testing with rigid_body - camera = OrthoGraphicCamera('camera', self.rigid_body, \ - near=1, far=1000) + # As compared to Perspective Camera, Orthographic Camera doesn't have + # fov, instead the left,right,top and bottom faces are adjusted by the + # Scene width, and height + + # Testing with rigid_body + camera = OrthoGraphicCamera('camera', self.rigid_body, near=1, + far=1000) assert camera.name == 'camera' assert camera.reference_frame == self.A @@ -293,23 +340,23 @@ def test_orthographic_camera(self): assert camera.near == 1 assert camera.far == 1000 - #Testing with reference_frame, particle - camera = OrthoGraphicCamera('camera', self.I, self.particle, \ - near=1, far=1000) + # Testing with reference_frame, particle + camera = OrthoGraphicCamera('camera', self.inertial_ref_frame, + self.particle, near=1, far=1000) assert camera.name == 'camera' - assert camera.reference_frame == self.I + assert camera.reference_frame == self.inertial_ref_frame assert camera.origin == self.P1 assert camera.near == 1 assert camera.far == 1000 - #Testing with reference_frame, point ... - camera = OrthoGraphicCamera('camera', self.I, self.O, near=1, \ - far=1000) + # Testing with reference_frame, point ... + camera = OrthoGraphicCamera('camera', self.inertial_ref_frame, + self.origin, near=1, far=1000) assert camera.name == 'camera' - assert camera.reference_frame == self.I - assert camera.origin == self.O + assert camera.reference_frame == self.inertial_ref_frame + assert camera.origin == self.origin assert camera.near == 1 assert camera.far == 1000 @@ -330,40 +377,67 @@ def test_orthographic_camera(self): camera.far = 500 assert camera.far == 500 - camera1 = OrthoGraphicCamera(self.I, self.O) + camera1 = OrthoGraphicCamera(self.inertial_ref_frame, self.origin) assert camera1.name == 'unnamed' - assert camera1.reference_frame == self.I - assert camera1.origin == self.O + assert camera1.reference_frame == self.inertial_ref_frame + assert camera1.origin == self.origin assert camera1.near == 1 assert camera1.far == 1000 + camera = OrthoGraphicCamera('camera', self.camera_ref_frame, + self.origin, near=1, far=1000) + camera.generate_transformation_matrix(self.inertial_ref_frame, + self.origin) + camera.generate_numeric_transform_function(self.dynamic, + self.parameters) + viz_matrix = camera.evaluate_transformation_matrix(self.states, + self.param_vals) + expected_viz_matrix = [[1., 0., 0., 0., + 0., 0., 1., 0., + 0., -1., 0., 0., + 0., 0., 0., 1.]] + assert_allclose(viz_matrix, expected_viz_matrix) + + scene_dict = camera.generate_scene_dict() + + assert scene_dict[id(camera)]['type'] == 'OrthoGraphicCamera' + assert scene_dict[id(camera)]['near'] == 1 + assert scene_dict[id(camera)]['far'] == 1000 + assert scene_dict[id(camera)]['name'] == 'camera' + assert scene_dict[id(camera)]['init_orientation'] == \ + [1., 0., 0., 0., + 0., 0., 1., 0., + 0., -1., 0., 0., + 0., 0., 0., 1.] + def test_point_light(self): - #Testing with rigid-body .. - light = PointLight('light', self.rigid_body, color='blue') + # Testing with rigid-body .. + light = PointLight('light', self.rigid_body, color='blue') assert light.name == 'light' assert light.reference_frame == self.A assert light.origin == self.P1 assert light.color == 'blue' - #Testing with reference_frame, particle .. - light = PointLight('light', self.I, self.particle, color='blue') + # Testing with reference_frame, particle .. + light = PointLight('light', self.inertial_ref_frame, self.particle, + color='blue') assert light.name == 'light' - assert light.reference_frame == self.I + assert light.reference_frame == self.inertial_ref_frame assert light.origin == self.P1 assert light.color == 'blue' - #Testing with reference_frame, point .. - light = PointLight('light', self.I, self.O, color='blue') + # Testing with reference_frame, point .. + light = PointLight('light', self.inertial_ref_frame, self.origin, + color='blue') assert light.name == 'light' - assert light.reference_frame == self.I - assert light.origin == self.O + assert light.reference_frame == self.inertial_ref_frame + assert light.origin == self.origin assert light.color == 'blue' - light.name = 'light1' assert light.name == 'light1' assert light.__str__() == 'PointLight: light1' @@ -378,22 +452,22 @@ def test_point_light(self): light.color = 'red' assert light.color == 'red' - - #Test unnamed - light1 = PointLight(self.I, self.O) + # Test unnamed + light1 = PointLight(self.inertial_ref_frame, self.origin) assert light1.name == 'unnamed' - assert light1.reference_frame == self.I - assert light1.origin == self.O + assert light1.reference_frame == self.inertial_ref_frame + assert light1.origin == self.origin assert light1.color == 'white' def test_scene_init(self): - self.scene2 = Scene(self.I, self.O, \ - self.global_frame1, self.global_frame2, \ - name='scene') + + self.scene2 = Scene(self.inertial_ref_frame, self.origin, + self.global_frame1, self.global_frame2, + name='scene') assert self.scene2.name == 'scene' - assert self.scene2.reference_frame == self.I - assert self.scene2.origin == self.O + assert self.scene2.reference_frame == self.inertial_ref_frame + assert self.scene2.origin == self.origin assert self.scene2.visualization_frames[0] is self.global_frame1 assert self.scene2.visualization_frames[1] is self.global_frame2 diff --git a/pydy/viz/visualization_frame.py b/pydy/viz/visualization_frame.py index 48ad92c1..1d1c6790 100644 --- a/pydy/viz/visualization_frame.py +++ b/pydy/viz/visualization_frame.py @@ -93,25 +93,26 @@ def __init__(self, *args): if isinstance(args[-1], Shape): self._shape = args[-1] else: - raise TypeError('''Please provide a valid shape object''') + raise TypeError("Please provide a valid shape object as the last " + " positional argument.") i = 0 - #If first arg is not str, name the visualization frame 'unnamed' + # If first arg is not str, name the visualization frame 'unnamed' if isinstance(args[i], str): - self._name = args[i] + self.name = args[i] i += 1 else: - self._name = 'unnamed' + self.name = 'unnamed' try: - self._reference_frame = args[i].get_frame() - self._origin = args[i].masscenter + self.reference_frame = args[i].get_frame() + self.origin = args[i].masscenter except AttributeError: #It is not a rigidbody, hence this arg should be a #reference frame try: dcm = args[i]._dcm_dict - self._reference_frame = args[i] + self.reference_frame = args[i] i += 1 except AttributeError: raise TypeError(''' A ReferenceFrame is to be supplied @@ -119,14 +120,14 @@ def __init__(self, *args): #Now next arg can either be a Particle or point try: - self._origin = args[i].point + self.origin = args[i].point except AttributeError: - self._origin = args[i] + self.origin = args[i] #setting attributes .. def __str__(self): - return 'VisualizationFrame ' + self._name + return 'VisualizationFrame ' + self.name def __repr__(self): return 'VisualizationFrame' @@ -363,10 +364,20 @@ def generate_scene_dict(self, constant_map={}): """ scene_dict = { id(self): {} } scene_dict[id(self)] = self.shape.generate_dict(constant_map=constant_map) - scene_dict[id(self)]["init_orientation"] = self._visualization_matrix[0] - scene_dict[id(self)]["reference_frame_name"] = str(self._reference_frame) + scene_dict[id(self)]['name'] = self.name + scene_dict[id(self)]["reference_frame_name"] = str(self.reference_frame) scene_dict[id(self)]["simulation_id"] = id(self) + try: + scene_dict[id(self)]["init_orientation"] = self._visualization_matrix[0] + except: + raise RuntimeError("Cannot generate visualization data " + \ + "because numerical transformation " + \ + "has not been performed, " + \ + "Please call the numerical " + \ + "transformation methods, " + \ + "before generating visualization dict") + return scene_dict def generate_simulation_dict(self): From 8a0f2e9f4a5a1028a8a67b6f5d03272de8457a7c Mon Sep 17 00:00:00 2001 From: "Jason K. Moore" Date: Sat, 2 Jan 2016 23:34:13 -0800 Subject: [PATCH 2/3] Made kwargs Python 2 compatible. --- pydy/viz/camera.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/pydy/viz/camera.py b/pydy/viz/camera.py index 96d0e11a..c77fd03b 100644 --- a/pydy/viz/camera.py +++ b/pydy/viz/camera.py @@ -22,7 +22,7 @@ class PerspectiveCamera(VisualizationFrame): """ - def __init__(self, *args, fov=45.0, near=1.0, far=1000.0): + def __init__(self, *args, **kwargs): """Initialises a PerspectiveCamera object. To initialize a PerspectiveCamera, one needs to supply a name (optional), a reference frame, a point, field of view (fov) (optional), near plane distance @@ -90,9 +90,12 @@ def __init__(self, *args, fov=45.0, near=1.0, far=1000.0): super(PerspectiveCamera, self).__init__(*args) - self.fov = fov - self.near = near - self.far = far + self.fov = 45.0 + self.near = 1.0 + self.far = 1000.0 + + for k, v in kwargs.items(): + setattr(self, k, v) def __str__(self): return 'PerspectiveCamera: ' + self.name @@ -162,7 +165,7 @@ class OrthoGraphicCamera(VisualizationFrame): """ - def __init__(self, *args, near=1.0, far=1000.0): + def __init__(self, *args, **kwargs): """Initialises a OrthoGraphicCameraCamera object. To initialize a OrthoGraphicCameraCamera, one needs to supply a name (optional), a reference frame, a point, field of view (fov) (optional), near plane @@ -226,8 +229,11 @@ def __init__(self, *args, near=1.0, far=1000.0): super(OrthoGraphicCamera, self).__init__(*args) - self.near = near - self.far = far + self.near = 1.0 + self.far = 1000.0 + + for k, v in kwargs.items(): + setattr(self, k, v) def __str__(self): return self.__class__.__name__ + ': ' + self.name From d142121e0ef10bd4adb1381228026adb9cd5c97e Mon Sep 17 00:00:00 2001 From: "Jason K. Moore" Date: Sun, 3 Jan 2016 08:51:05 -0800 Subject: [PATCH 3/3] Added release note for PR 319. [skip ci] --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 4fa7e163..6bf17a51 100644 --- a/README.rst +++ b/README.rst @@ -379,9 +379,11 @@ Release Notes - The Cython backend now compiles on Windows. [PR `#313`_] - CI testing is now run on appveyor with Windows VMs. [PR `#315`_] - Added a verbose option to the Cython compilation. [PR `#315`_] +- Improved the camera code through inheritance [PR `#319`_] .. _#313: https://github.com/pydy/pydy/pull/313 .. _#315: https://github.com/pydy/pydy/pull/315 +.. _#319: https://github.com/pydy/pydy/pull/319 0.3.0 -----