123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747 |
- # coding=utf-8
- from abc import ABC, abstractmethod
- from sympy.core.numbers import pi
- from sympy.physics.mechanics.body import Body
- from sympy.physics.vector import Vector, dynamicsymbols, cross
- from sympy.physics.vector.frame import ReferenceFrame
- import warnings
- __all__ = ['Joint', 'PinJoint', 'PrismaticJoint']
- class Joint(ABC):
- """Abstract base class for all specific joints.
- Explanation
- ===========
- A joint subtracts degrees of freedom from a body. This is the base class
- for all specific joints and holds all common methods acting as an interface
- for all joints. Custom joint can be created by inheriting Joint class and
- defining all abstract functions.
- The abstract methods are:
- - ``_generate_coordinates``
- - ``_generate_speeds``
- - ``_orient_frames``
- - ``_set_angular_velocity``
- - ``_set_linar_velocity``
- Parameters
- ==========
- name : string
- A unique name for the joint.
- parent : Body
- The parent body of joint.
- child : Body
- The child body of joint.
- coordinates: List of dynamicsymbols, optional
- Generalized coordinates of the joint.
- speeds : List of dynamicsymbols, optional
- Generalized speeds of joint.
- parent_joint_pos : Vector, optional
- Vector from the parent body's mass center to the point where the parent
- and child are connected. The default value is the zero vector.
- child_joint_pos : Vector, optional
- Vector from the child body's mass center to the point where the parent
- and child are connected. The default value is the zero vector.
- parent_axis : Vector, optional
- Axis fixed in the parent body which aligns with an axis fixed in the
- child body. The default is x axis in parent's reference frame.
- child_axis : Vector, optional
- Axis fixed in the child body which aligns with an axis fixed in the
- parent body. The default is x axis in child's reference frame.
- Attributes
- ==========
- name : string
- The joint's name.
- parent : Body
- The joint's parent body.
- child : Body
- The joint's child body.
- coordinates : list
- List of the joint's generalized coordinates.
- speeds : list
- List of the joint's generalized speeds.
- parent_point : Point
- The point fixed in the parent body that represents the joint.
- child_point : Point
- The point fixed in the child body that represents the joint.
- parent_axis : Vector
- The axis fixed in the parent frame that represents the joint.
- child_axis : Vector
- The axis fixed in the child frame that represents the joint.
- kdes : list
- Kinematical differential equations of the joint.
- Notes
- =====
- The direction cosine matrix between the child and parent is formed using a
- simple rotation about an axis that is normal to both ``child_axis`` and
- ``parent_axis``. In general, the normal axis is formed by crossing the
- ``child_axis`` into the ``parent_axis`` except if the child and parent axes
- are in exactly opposite directions. In that case the rotation vector is chosen
- using the rules in the following table where ``C`` is the child reference
- frame and ``P`` is the parent reference frame:
- .. list-table::
- :header-rows: 1
- * - ``child_axis``
- - ``parent_axis``
- - ``rotation_axis``
- * - ``-C.x``
- - ``P.x``
- - ``P.z``
- * - ``-C.y``
- - ``P.y``
- - ``P.x``
- * - ``-C.z``
- - ``P.z``
- - ``P.y``
- * - ``-C.x-C.y``
- - ``P.x+P.y``
- - ``P.z``
- * - ``-C.y-C.z``
- - ``P.y+P.z``
- - ``P.x``
- * - ``-C.x-C.z``
- - ``P.x+P.z``
- - ``P.y``
- * - ``-C.x-C.y-C.z``
- - ``P.x+P.y+P.z``
- - ``(P.x+P.y+P.z) × P.x``
- """
- def __init__(self, name, parent, child, coordinates=None, speeds=None,
- parent_joint_pos=None, child_joint_pos=None, parent_axis=None,
- child_axis=None):
- if not isinstance(name, str):
- raise TypeError('Supply a valid name.')
- self._name = name
- if not isinstance(parent, Body):
- raise TypeError('Parent must be an instance of Body.')
- self._parent = parent
- if not isinstance(child, Body):
- raise TypeError('Parent must be an instance of Body.')
- self._child = child
- self._coordinates = self._generate_coordinates(coordinates)
- self._speeds = self._generate_speeds(speeds)
- self._kdes = self._generate_kdes()
- self._parent_axis = self._axis(parent, parent_axis)
- self._child_axis = self._axis(child, child_axis)
- self._parent_point = self._locate_joint_pos(parent, parent_joint_pos)
- self._child_point = self._locate_joint_pos(child, child_joint_pos)
- self._orient_frames()
- self._set_angular_velocity()
- self._set_linear_velocity()
- def __str__(self):
- return self.name
- def __repr__(self):
- return self.__str__()
- @property
- def name(self):
- return self._name
- @property
- def parent(self):
- """Parent body of Joint."""
- return self._parent
- @property
- def child(self):
- """Child body of Joint."""
- return self._child
- @property
- def coordinates(self):
- """List generalized coordinates of the joint."""
- return self._coordinates
- @property
- def speeds(self):
- """List generalized coordinates of the joint.."""
- return self._speeds
- @property
- def kdes(self):
- """Kinematical differential equations of the joint."""
- return self._kdes
- @property
- def parent_axis(self):
- """The axis of parent frame."""
- return self._parent_axis
- @property
- def child_axis(self):
- """The axis of child frame."""
- return self._child_axis
- @property
- def parent_point(self):
- """The joint's point where parent body is connected to the joint."""
- return self._parent_point
- @property
- def child_point(self):
- """The joint's point where child body is connected to the joint."""
- return self._child_point
- @abstractmethod
- def _generate_coordinates(self, coordinates):
- """Generate list generalized coordinates of the joint."""
- pass
- @abstractmethod
- def _generate_speeds(self, speeds):
- """Generate list generalized speeds of the joint."""
- pass
- @abstractmethod
- def _orient_frames(self):
- """Orient frames as per the joint."""
- pass
- @abstractmethod
- def _set_angular_velocity(self):
- pass
- @abstractmethod
- def _set_linear_velocity(self):
- pass
- def _generate_kdes(self):
- kdes = []
- t = dynamicsymbols._t
- for i in range(len(self.coordinates)):
- kdes.append(-self.coordinates[i].diff(t) + self.speeds[i])
- return kdes
- def _axis(self, body, ax):
- if ax is None:
- ax = body.frame.x
- return ax
- if not isinstance(ax, Vector):
- raise TypeError("Axis must be of type Vector.")
- if not ax.dt(body.frame) == 0:
- msg = ('Axis cannot be time-varying when viewed from the '
- 'associated body.')
- raise ValueError(msg)
- return ax
- def _locate_joint_pos(self, body, joint_pos):
- if joint_pos is None:
- joint_pos = Vector(0)
- if not isinstance(joint_pos, Vector):
- raise ValueError('Joint Position must be supplied as Vector.')
- if not joint_pos.dt(body.frame) == 0:
- msg = ('Position Vector cannot be time-varying when viewed from '
- 'the associated body.')
- raise ValueError(msg)
- point_name = self._name + '_' + body.name + '_joint'
- return body.masscenter.locatenew(point_name, joint_pos)
- def _alignment_rotation(self, parent, child):
- # Returns the axis and angle between two axis(vectors).
- angle = parent.angle_between(child)
- axis = cross(child, parent).normalize()
- return angle, axis
- def _generate_vector(self):
- parent_frame = self.parent.frame
- components = self.parent_axis.to_matrix(parent_frame)
- x, y, z = components[0], components[1], components[2]
- if x != 0:
- if y!=0:
- if z!=0:
- return cross(self.parent_axis,
- parent_frame.x)
- if z!=0:
- return parent_frame.y
- return parent_frame.z
- if x == 0:
- if y!=0:
- if z!=0:
- return parent_frame.x
- return parent_frame.x
- return parent_frame.y
- def _set_orientation(self):
- #Helper method for `orient_axis()`
- self.child.frame.orient_axis(self.parent.frame, self.parent_axis, 0)
- angle, axis = self._alignment_rotation(self.parent_axis,
- self.child_axis)
- with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=UserWarning)
- if axis != Vector(0) or angle == pi:
- if angle == pi:
- axis = self._generate_vector()
- int_frame = ReferenceFrame('int_frame')
- int_frame.orient_axis(self.child.frame, self.child_axis, 0)
- int_frame.orient_axis(self.parent.frame, axis, angle)
- return int_frame
- return self.parent.frame
- class PinJoint(Joint):
- """Pin (Revolute) Joint.
- Explanation
- ===========
- A pin joint is defined such that the joint rotation axis is fixed in both
- the child and parent and the location of the joint is relative to the mass
- center of each body. The child rotates an angle, θ, from the parent about
- the rotation axis and has a simple angular speed, ω, relative to the
- parent. The direction cosine matrix between the child and parent is formed
- using a simple rotation about an axis that is normal to both ``child_axis``
- and ``parent_axis``, see the Notes section for a detailed explanation of
- this.
- Parameters
- ==========
- name : string
- A unique name for the joint.
- parent : Body
- The parent body of joint.
- child : Body
- The child body of joint.
- coordinates: dynamicsymbol, optional
- Generalized coordinates of the joint.
- speeds : dynamicsymbol, optional
- Generalized speeds of joint.
- parent_joint_pos : Vector, optional
- Vector from the parent body's mass center to the point where the parent
- and child are connected. The default value is the zero vector.
- child_joint_pos : Vector, optional
- Vector from the child body's mass center to the point where the parent
- and child are connected. The default value is the zero vector.
- parent_axis : Vector, optional
- Axis fixed in the parent body which aligns with an axis fixed in the
- child body. The default is x axis in parent's reference frame.
- child_axis : Vector, optional
- Axis fixed in the child body which aligns with an axis fixed in the
- parent body. The default is x axis in child's reference frame.
- Attributes
- ==========
- name : string
- The joint's name.
- parent : Body
- The joint's parent body.
- child : Body
- The joint's child body.
- coordinates : list
- List of the joint's generalized coordinates.
- speeds : list
- List of the joint's generalized speeds.
- parent_point : Point
- The point fixed in the parent body that represents the joint.
- child_point : Point
- The point fixed in the child body that represents the joint.
- parent_axis : Vector
- The axis fixed in the parent frame that represents the joint.
- child_axis : Vector
- The axis fixed in the child frame that represents the joint.
- kdes : list
- Kinematical differential equations of the joint.
- Examples
- =========
- A single pin joint is created from two bodies and has the following basic
- attributes:
- >>> from sympy.physics.mechanics import Body, PinJoint
- >>> parent = Body('P')
- >>> parent
- P
- >>> child = Body('C')
- >>> child
- C
- >>> joint = PinJoint('PC', parent, child)
- >>> joint
- PinJoint: PC parent: P child: C
- >>> joint.name
- 'PC'
- >>> joint.parent
- P
- >>> joint.child
- C
- >>> joint.parent_point
- PC_P_joint
- >>> joint.child_point
- PC_C_joint
- >>> joint.parent_axis
- P_frame.x
- >>> joint.child_axis
- C_frame.x
- >>> joint.coordinates
- [theta_PC(t)]
- >>> joint.speeds
- [omega_PC(t)]
- >>> joint.child.frame.ang_vel_in(joint.parent.frame)
- omega_PC(t)*P_frame.x
- >>> joint.child.frame.dcm(joint.parent.frame)
- Matrix([
- [1, 0, 0],
- [0, cos(theta_PC(t)), sin(theta_PC(t))],
- [0, -sin(theta_PC(t)), cos(theta_PC(t))]])
- >>> joint.child_point.pos_from(joint.parent_point)
- 0
- To further demonstrate the use of the pin joint, the kinematics of simple
- double pendulum that rotates about the Z axis of each connected body can be
- created as follows.
- >>> from sympy import symbols, trigsimp
- >>> from sympy.physics.mechanics import Body, PinJoint
- >>> l1, l2 = symbols('l1 l2')
- First create bodies to represent the fixed ceiling and one to represent
- each pendulum bob.
- >>> ceiling = Body('C')
- >>> upper_bob = Body('U')
- >>> lower_bob = Body('L')
- The first joint will connect the upper bob to the ceiling by a distance of
- ``l1`` and the joint axis will be about the Z axis for each body.
- >>> ceiling_joint = PinJoint('P1', ceiling, upper_bob,
- ... child_joint_pos=-l1*upper_bob.frame.x,
- ... parent_axis=ceiling.frame.z,
- ... child_axis=upper_bob.frame.z)
- The second joint will connect the lower bob to the upper bob by a distance
- of ``l2`` and the joint axis will also be about the Z axis for each body.
- >>> pendulum_joint = PinJoint('P2', upper_bob, lower_bob,
- ... child_joint_pos=-l2*lower_bob.frame.x,
- ... parent_axis=upper_bob.frame.z,
- ... child_axis=lower_bob.frame.z)
- Once the joints are established the kinematics of the connected bodies can
- be accessed. First the direction cosine matrices of pendulum link relative
- to the ceiling are found:
- >>> upper_bob.frame.dcm(ceiling.frame)
- Matrix([
- [ cos(theta_P1(t)), sin(theta_P1(t)), 0],
- [-sin(theta_P1(t)), cos(theta_P1(t)), 0],
- [ 0, 0, 1]])
- >>> trigsimp(lower_bob.frame.dcm(ceiling.frame))
- Matrix([
- [ cos(theta_P1(t) + theta_P2(t)), sin(theta_P1(t) + theta_P2(t)), 0],
- [-sin(theta_P1(t) + theta_P2(t)), cos(theta_P1(t) + theta_P2(t)), 0],
- [ 0, 0, 1]])
- The position of the lower bob's masscenter is found with:
- >>> lower_bob.masscenter.pos_from(ceiling.masscenter)
- l1*U_frame.x + l2*L_frame.x
- The angular velocities of the two pendulum links can be computed with
- respect to the ceiling.
- >>> upper_bob.frame.ang_vel_in(ceiling.frame)
- omega_P1(t)*C_frame.z
- >>> lower_bob.frame.ang_vel_in(ceiling.frame)
- omega_P1(t)*C_frame.z + omega_P2(t)*U_frame.z
- And finally, the linear velocities of the two pendulum bobs can be computed
- with respect to the ceiling.
- >>> upper_bob.masscenter.vel(ceiling.frame)
- l1*omega_P1(t)*U_frame.y
- >>> lower_bob.masscenter.vel(ceiling.frame)
- l1*omega_P1(t)*U_frame.y + l2*(omega_P1(t) + omega_P2(t))*L_frame.y
- """
- def __init__(self, name, parent, child, coordinates=None, speeds=None,
- parent_joint_pos=None, child_joint_pos=None, parent_axis=None,
- child_axis=None):
- super().__init__(name, parent, child, coordinates, speeds,
- parent_joint_pos, child_joint_pos, parent_axis,
- child_axis)
- def __str__(self):
- return (f'PinJoint: {self.name} parent: {self.parent} '
- f'child: {self.child}')
- def _generate_coordinates(self, coordinate):
- coordinates = []
- if coordinate is None:
- theta = dynamicsymbols('theta' + '_' + self._name)
- coordinate = theta
- coordinates.append(coordinate)
- return coordinates
- def _generate_speeds(self, speed):
- speeds = []
- if speed is None:
- omega = dynamicsymbols('omega' + '_' + self._name)
- speed = omega
- speeds.append(speed)
- return speeds
- def _orient_frames(self):
- frame = self._set_orientation()
- self.child.frame.orient_axis(frame, self.parent_axis,
- self.coordinates[0])
- def _set_angular_velocity(self):
- self.child.frame.set_ang_vel(self.parent.frame, self.speeds[0] *
- self.parent_axis.normalize())
- def _set_linear_velocity(self):
- self.parent_point.set_vel(self.parent.frame, 0)
- self.child_point.set_vel(self.parent.frame, 0)
- self.child_point.set_pos(self.parent_point, 0)
- self.child.masscenter.v2pt_theory(self.parent.masscenter,
- self.parent.frame, self.child.frame)
- class PrismaticJoint(Joint):
- """Prismatic (Sliding) Joint.
- Explanation
- ===========
- It is defined such that the child body translates with respect to the parent
- body along the body fixed parent axis. The location of the joint is defined
- by two points in each body which coincides when the generalized coordinate is zero. The direction cosine matrix between
- the child and parent is formed using a simple rotation about an axis that is normal to
- both ``child_axis`` and ``parent_axis``, see the Notes section for a detailed explanation of
- this.
- Parameters
- ==========
- name : string
- A unique name for the joint.
- parent : Body
- The parent body of joint.
- child : Body
- The child body of joint.
- coordinates: dynamicsymbol, optional
- Generalized coordinates of the joint.
- speeds : dynamicsymbol, optional
- Generalized speeds of joint.
- parent_joint_pos : Vector, optional
- Vector from the parent body's mass center to the point where the parent
- and child are connected. The default value is the zero vector.
- child_joint_pos : Vector, optional
- Vector from the child body's mass center to the point where the parent
- and child are connected. The default value is the zero vector.
- parent_axis : Vector, optional
- Axis fixed in the parent body which aligns with an axis fixed in the
- child body. The default is x axis in parent's reference frame.
- child_axis : Vector, optional
- Axis fixed in the child body which aligns with an axis fixed in the
- parent body. The default is x axis in child's reference frame.
- Attributes
- ==========
- name : string
- The joint's name.
- parent : Body
- The joint's parent body.
- child : Body
- The joint's child body.
- coordinates : list
- List of the joint's generalized coordinates.
- speeds : list
- List of the joint's generalized speeds.
- parent_point : Point
- The point fixed in the parent body that represents the joint.
- child_point : Point
- The point fixed in the child body that represents the joint.
- parent_axis : Vector
- The axis fixed in the parent frame that represents the joint.
- child_axis : Vector
- The axis fixed in the child frame that represents the joint.
- kdes : list
- Kinematical differential equations of the joint.
- Examples
- =========
- A single prismatic joint is created from two bodies and has the following basic
- attributes:
- >>> from sympy.physics.mechanics import Body, PrismaticJoint
- >>> parent = Body('P')
- >>> parent
- P
- >>> child = Body('C')
- >>> child
- C
- >>> joint = PrismaticJoint('PC', parent, child)
- >>> joint
- PrismaticJoint: PC parent: P child: C
- >>> joint.name
- 'PC'
- >>> joint.parent
- P
- >>> joint.child
- C
- >>> joint.parent_point
- PC_P_joint
- >>> joint.child_point
- PC_C_joint
- >>> joint.parent_axis
- P_frame.x
- >>> joint.child_axis
- C_frame.x
- >>> joint.coordinates
- [x_PC(t)]
- >>> joint.speeds
- [v_PC(t)]
- >>> joint.child.frame.ang_vel_in(joint.parent.frame)
- 0
- >>> joint.child.frame.dcm(joint.parent.frame)
- Matrix([
- [1, 0, 0],
- [0, 1, 0],
- [0, 0, 1]])
- >>> joint.child_point.pos_from(joint.parent_point)
- x_PC(t)*P_frame.x
- To further demonstrate the use of the prismatic joint, the kinematics of
- two masses sliding, one moving relative to a fixed body and the other relative to the
- moving body. about the X axis of each connected body can be created as follows.
- >>> from sympy.physics.mechanics import PrismaticJoint, Body
- First create bodies to represent the fixed ceiling and one to represent
- a particle.
- >>> wall = Body('W')
- >>> Part1 = Body('P1')
- >>> Part2 = Body('P2')
- The first joint will connect the particle to the ceiling and the
- joint axis will be about the X axis for each body.
- >>> J1 = PrismaticJoint('J1', wall, Part1)
- The second joint will connect the second particle to the first particle
- and the joint axis will also be about the X axis for each body.
- >>> J2 = PrismaticJoint('J2', Part1, Part2)
- Once the joint is established the kinematics of the connected bodies can
- be accessed. First the direction cosine matrices of Part relative
- to the ceiling are found:
- >>> Part1.dcm(wall)
- Matrix([
- [1, 0, 0],
- [0, 1, 0],
- [0, 0, 1]])
- >>> Part2.dcm(wall)
- Matrix([
- [1, 0, 0],
- [0, 1, 0],
- [0, 0, 1]])
- The position of the particles' masscenter is found with:
- >>> Part1.masscenter.pos_from(wall.masscenter)
- x_J1(t)*W_frame.x
- >>> Part2.masscenter.pos_from(wall.masscenter)
- x_J1(t)*W_frame.x + x_J2(t)*P1_frame.x
- The angular velocities of the two particle links can be computed with
- respect to the ceiling.
- >>> Part1.ang_vel_in(wall)
- 0
- >>> Part2.ang_vel_in(wall)
- 0
- And finally, the linear velocities of the two particles can be computed
- with respect to the ceiling.
- >>> Part1.masscenter_vel(wall)
- v_J1(t)*W_frame.x
- >>> Part2.masscenter.vel(wall.frame)
- v_J1(t)*W_frame.x + v_J2(t)*P1_frame.x
- """
- def __init__(self, name, parent, child, coordinates=None, speeds=None, parent_joint_pos=None,
- child_joint_pos=None, parent_axis=None, child_axis=None):
- super().__init__(name, parent, child, coordinates, speeds, parent_joint_pos,
- child_joint_pos, parent_axis, child_axis)
- def __str__(self):
- return (f'PrismaticJoint: {self.name} parent: {self.parent} '
- f'child: {self.child}')
- def _generate_coordinates(self, coordinate):
- coordinates = []
- if coordinate is None:
- x = dynamicsymbols('x' + '_' + self._name)
- coordinate = x
- coordinates.append(coordinate)
- return coordinates
- def _generate_speeds(self, speed):
- speeds = []
- if speed is None:
- y = dynamicsymbols('v' + '_' + self._name)
- speed = y
- speeds.append(speed)
- return speeds
- def _orient_frames(self):
- frame = self._set_orientation()
- self.child.frame.orient_axis(frame, self.parent_axis, 0)
- def _set_angular_velocity(self):
- self.child.frame.set_ang_vel(self.parent.frame, 0)
- def _set_linear_velocity(self):
- self.parent_point.set_vel(self.parent.frame, 0)
- self.child_point.set_vel(self.child.frame, 0)
- self.child_point.set_pos(self.parent_point, self.coordinates[0] * self.parent_axis.normalize())
- self.child_point.set_vel(self.parent.frame, self.speeds[0] * self.parent_axis.normalize())
- self.child.masscenter.set_vel(self.parent.frame, self.speeds[0] * self.parent_axis.normalize())
|