Commit 68ebdf4b authored by florent's avatar florent
Browse files

Added "Robotic component" page with links to GenoM and OpenRTM components.

parent 356bedc0
2008/06/03
14. Added "Robotic component" page with links to GenoM and OpenRTM components.
2008/05/28
13 renamed directory doc/abstractRobotsDynamics to doc/abstractRobotDynamics.
13. Renamed directory doc/abstractRobotsDynamics to doc/abstractRobotDynamics.
12. Added link to hrp2Dynamics
2008/03/06
11. Added link to hppTimingPlanner.
......
......@@ -65,6 +65,9 @@ PKG_CHKADDDPD(SLAM3DPLANNER, slam3DPlanner)
PKG_CHKADDDPD(HPPENVGENERATOR, hppEnvGenerator)
PKG_CHKADDDPD(WORLDMODELGRID3D, worldModelGrid3D)
PKG_CHKADDDPD(HRP2DYNAMICS, hrp2Dynamics)
PKG_CHKADDDPD(HPPWALKPLANNERCOMPSPEC, hppWalkPlannerCompSpec)
PKG_CHKADDDPD(WALKPLANNERORTM, walkPlannerOrtm)
PKG_CHKADDDPD(WALKPLANNERORTMCLIENT, walkPlannerOrtmClient)
# --- generate files -------------------------------------------------
......@@ -75,6 +78,7 @@ AC_OUTPUT(
doc/html/corba.html
doc/html/algorithm.html
doc/html/kppInterface.html
doc/html/roboticComponent.html
doc/html/main.html
doc/html/index.html
doc/html/tree.html
......
......@@ -48,7 +48,8 @@ hppDoc_DATA=\
html/tree.html \
html/corba.html \
html/index.html \
html/kppInterface.html
html/kppInterface.html \
html/roboticComponent.html
# the list of pages that explain how to install a package if it is not
# installed yet. These pages are replaced (in the above templates) by a
......
/**
\page hppDoc_development Developing in Hpp
This page explains some development rules applied in Hpp project. They are mostly common sense rules.
\section hppDoc_rules General rules to be followed in HPP developement
\subsection hppDoc_separate_algo_middleware Make clear distinction between algorithms, middleware and graphical interface
\image html archi.png "Architecture of HPP: the functionalities are distributed into separate software packages. The architecture is composed of Three types of packages: algorithms, CORBA interfaces and KPP-SDK interfaces."
\image latex archi.pdf "Architecture of HPP: the functionalities are distributed into separate software packages. The architecture is composed of Three types of packages: algorithms, CORBA interfaces and KPP-SDK interfaces."
HPP is composed of several software packages divided into three groups as explained in the above figure:
\li algorithms,
\li Corba server
\li KPP-interfaces
KPP-interface and Corba server should be considered as visualization and debugging-testing tools.
It is mostly important that packages implementing path planning algorithms for humanoid robots are
independent from a given middleware (CORBA) and from a given GUI (KineoPathPlanner). As a consequence,
no CORBA::xxx attribute should be in a class belonging to the algorithm part.
This simple principle will enable to easily insert the algorithmic software packages into different middlewares (GenoM, RT-middleware for instance).
\subsection hppDoc_small_packages Modularity
Try to avoid developing huge packages including many functions. Instead, build several small packages
with simple interfaces and easy to understand functionalities.
\subsubsection hppDoc_example1 Example
Let us assume that you are developing a path planning algorithm and you want to use quaternions to represent the orientations of rigid-bodies.
\li The first step consists in trying to find an existing implementation of quaternions that you can use.
\li Let us assume (this is very unlikeky) that you cannot find a good implementation of quaternions, then, instead of developing operations on quaternions in your path planning package, it is much more clever to create a package that will handle quaternion operations and to make your path planning package depend on it. Later, other users will be able to use your quaternion package.
\subsection hppDoc_level_generality Level of generality
When you implement an algorithm, always ask yourself the question: "Could my algorihtm be applied to applications more general than the one I am dealing with?"
If yes, try to make your algorithm take more general input than your practical problem of today.
\subsubsection hppDoc_example2 Example
Let us assume that you want to implement Newton algoritm to find a root of a polynomial function.
Your algorithm requires the derivative of the polynomial.
You can get an expression of a polynomial derivative using the polynomial coefficients.
However, it would be more clever to develop the same algorithm taking as input a function that might not be a polynomial.
For that you can define an abstract class
\code
class Cmapping {
public:
virtual double value(double inParamter) = 0;
virtual double derivative(double inParamter) = 0;
};
\endcode
make your Newton implementation take as input an object \c Cmapping and then derive this class into a concrete polynomial class.
Thus, your algorithm can be used by other people wanting to find the root of non-polynomial functions.
\subsubsection hppDoc_humanoid Humanoid robots
The algorithms we develop are mostly applied to one type of humanoid robot: HRP2.
It is therefore important to develop these algorithms in such a way that they can be
applied to any other humanoid robot. For that developers should avoid to make too strong asumptions
about the robot structure. The abstract CjrlHumanoidDynamicRobot interface for dynamic humanoid robots have been designed in this aim.
\section hppDoc_howto How to implement a new algorithm in HPP.
To implement a new algorithm in HPP, you need to create new software packages as described below.
To create new software packages, we advise developers to use perl script \c packageCreate.
\code
[~] cd devel/src
[src] cg-clone git+ssh://[git|softs].laas.fr/git/robots/scripts
\endcode
\subsection hppDoc_new_algo Create a new software package implementing your algorithm
To create a new package depending on \c hppCore, type the following commands.
\code
[~] cd devel/src
[src] perl ./scripts/packageCreate hppNewAlgo -d hppCore HPPCORE
\endcode
If you want your package to depend on other packages add -d package PACKAGE for each dependence.
This operation creates a template of software package with all necessary files to compile. There are four subdirectories in this package:
\li \c doc: contains necessary files to generate doxygen documentation,
\li \c include contains headers files
\li \c src contains source code files
\li \c unitTesting contains files used to test the algorithm developed in the package.
Define in <tt>include/hppNewAlgo.h</tt> a class that derives from ChppPlanner.
\code
#include "hppPlanner.h"
class ChppNewAlgo : public ChppPlanner {
public:
...
ktStatus solve();
};
\endcode
Class ChppPlanner proposes an interface function to insert a robot:
\code
ktStatus ChppPlanner::addHppProblem(CkppDeviceComponentShPtr robot);
\endcode
Independently from how the robot is inserted into the object, you can use
it as the input of your algorithm in your class ChppNewAlgo:
\code
CkppDeviceComponentShPtr robot = robotIthProblem(0);
\endcode
Write in <tt>src/hppNewAlgo.cpp</tt> function
\code
ktStatus ChppNewAlgo::solve()
{
CkwsPath path = resultOfNewAlgo();
ChppProblem& hppProblem = hppProblemVector[problemId];
hppProblem.addPath(kwsPath);
}
\endcode
that runs your algorithm. The two last lines insert the result of your path in KPP interface if the interface is running.
To compile and install your package do the following step:
\code
[~] cd devel/src/hppNewAlgo
[hppNewAlgo] aclocal
[hppNewAlgo] libtoolize -c
[hppNewAlgo] autoconf
[hppNewAlgo] automake -ac
[hppNewAlgo] mkdir build
[hppNewAlgo] cd build
[hppNewAlgo] ../configure --prefix=${HOME}/devel
[hppNewAlgo] make
[hppNewAlgo] make install
\endcode
\subsection hppDoc_new_interface Create a new KPP-interface for your package
To be able to see the result of your algorithm, you need to create a new KPP-interface deriving from CkppInterface
\code
[~] cd devel/src
[src] perl ./scripts/packageCreate kppInterfaceNewAlgo -d kppInterface KPPINTERFACE -d hppNewAlgo HPPNEWALGO
\endcode
and depending on your algorithm software package.
See package \c kppInterfaceTutorial for an example, and especially for managing Kineo license issue in <tt>src/Makefile.am</tt>
\code
[~] cd devel/src
[src] cg-clone git+ssh://[git|softs].laas.fr/git/jrl/hppTutorialPlanner
[src] cg-clone git+ssh://[git|softs].laas.fr/git/jrl/kppInterfaceTutorial
\endcode
To run your interface into KineoPathPlanner, do the following:
\code
[~] KineoPathPlanner -ModulePath ${HOME}/lib/modules/${HOST}/libkppInterfaceNewAlgo
\endcode
*/
/**
\page hppDoc_howToInstall How to install HPP
\section hppDoc_install_intro Introduction
HPP (Humanoid Path Planner) is composed of several software modules packaged by autotools.
These software modules are stored in a git repositories on git.laas.fr (or softs.laas.fr) for people working at LAAS.
There are mainly two types of packages in HPP:
\li packages under development that are not stable yet: each developer needs to download his own version using git and commit his modifications.
\li packages that are more stable and for which versions have already been released: these packages are automatically installed using a tool called \c robotpkg.
\section hppDoc_installing Installation procedure
\subsection hppDoc_preliminary Preliminary remarks
\subsubsection hppDoc_subsubsec_env Environment variables
Along the installation procedure, some environment variables are defined. It is recommended to include these definitions into an initialization file like <tt>.tcshrc</tt> or <tt>.bashrc</tt> so that they will be defined at each new session.
\subsubsection hppDoc_subsubsec_kineo About KineoWorks
To install KineoWorks, you will be asked for two archives:
\li KPPSDK_2.04.300.tgz and
\li KPPSDK_2.04.301.tgz
You need to ask these archives to <a href="mailto:anthony.mallet@laas.fr">Anthony Mallet</a>.
\subsection hppDoc_robotpkg Robotpkg
The first step consists in installing \c robotpkg on your account. For that, you need to create a directory where \c robotpkg will install all necessary packages:
\code
[~] mkdir ${HOME}/openrobots
[~] cd ${HOME}/openrobots
\endcode
Then download a working version of \c robotpkg
\code
[openrobots] cg-clone git+ssh://[git|softs].laas.fr/git/robots/robotpkg
\endcode
Note that from now on, <tt> [git|softs] </tt> should be replaced by
\li \c softs if you are working on a LAAS account,
\li \c git if you are working from a distant site.
Set the following environment variable:
\code
[openrobots] setenv ROBOTPKG_BASE ${HOME}/openrobots
\endcode
Install \c robotpkg:
\code
[openrobots] cd robotpkg
[robotpkg] ./bootstrap/bootstrap --prefix=${HOME}/openrobots
\endcode
Then follow the instructions.
To install a package, go into <tt>${HOME}/openrobots/robotpkg/path-to-package</tt> and type <tt>make update</tt>
For instance
\code
[robotpkg] cd path/hpp-core
[hpp-core] make update
\endcode
All dependencies are automatically installed. For some packages, you will need to follow instructions to accept license agreement. For instance KineoWorks installation will output the following message:
\code
ERROR: kineo-pp-2.04.301r3 has an unacceptable license: kineocam-license.
ERROR: To view the license, enter "make show-license".
ERROR: To indicate acceptance, add this line to
ERROR: ${HOME}/openrobots/etc/robotpkg.conf:
ERROR: ACCEPTABLE_LICENSES+=kineocam-license
\endcode
Follow the instruction and type again:
\code
[hpp-core] make update
\endcode
Once modules managed by \c robotpkg have been installed, you can download working versions of packages under development or create your own package.
\subsection hppDoc_other_packages Packages under development or new packages.
To download, compile and install packages not managed by \c robotpkg, or packages that you want to modify or debug, create a development directory as follows:
\code
[~] mkdir ${HOME}/devel
[~] cd ${HOME}/devel
\endcode
Then download the packages you need
\code
[devel] cg-clone git+ssh://[git|softs].laas.fr/git/jrl/name-of-package
\endcode
Each package (managed by \c robotpkg or not) installs a file with extension <tt>.pc</tt> in <tt>prefix/lib/pkgconfig</tt> where <tt>prefix</tt> is the installation prefix of the package. This file stores information about the package and its dependencies.
This information is retrieved by \c pkg-config executable. \c pkg-config searches for <tt>.pc</tt> files in paths encoded in \c PKG_CONFIG_PATH environment variable.
You need therefore to set this variable correctly, for instance:
\code
[devel] setenv PKG_CONFIG_PATH ${HOME}/devel/lib/pkgconfig:${HOME}/openrobots/lib/pkgconfig:/usr/local/lib/pkgconfig
\endcode
Compile and install the packages in your development directory
\code
[devel] cd package
[package] aclocal
[package] libtoolize -c
[package] autoconf
[package] automake -ac
[package] mkdir build
[package] cd build
[build] ../configure --prefix=${HOME}/devel
[build] make
[build] make install
\endcode
\subsection hppDoc_omniORB Configuring CORBA and using hppCorbaServer
\c hppCorbaServer is a package that enables developers to run their algorithms from external application like python scripts for instance.
This package instanciates a 3 CORBA objects that can handle requests trigerring actions in HPP. The CORBA objects correspond to the following interfaces:
\li <tt>ChppciRobot</tt> to define and build robots,
\li <tt>ChppciObstacle</tt> to define obstacles,
\li <tt>ChppciProblem</tt> to define and solve path planning problems.
The package also optionally implements an OpenHRP client (configuration option --with-openhrp) that enables the user to download HRP2 model by a Corba request.
\c hppCorbaServer is based on omniORB4 (installed by robotpkg), an implementation of Corba. We will explain now how to configure omniORB4 in order to be able
\li to load HRP2 model from OpenHRP
\li to instanciate a Corba server to control your program.
\subsubsection hppDoc_name_server The name server
Corba objects are defined by names. In order to communicate between objects, Corba needs to map names with objects, that can run on distant machines. This is the role of the name server. Before starting launching Corba objects, you thus need to start a name server.
A name server is identified by the machine on which it runs and by a port id on this machine. You thus need to choose a port id on your machine.
In order to avoid conflicts when several people work on the same machine, we usually use as port id: (user id + 1024). We strongly recommend that you follow this procedure.
Type then
\code
[~] id -u
\endcode in a terminal and you get your user id on your machine. Add 1024 to this number, you get your port id.
Then create a directory to store omniORB information
\code
[~] mkdir ${HOME}/omniORB
[~] cd ${HOME}/omniORB
\endcode
In this directory, create a file <tt>omniORG.cfg</tt>
and copy the following line in this file:
\code
# Configuration file for omniOrb
# environment variable OMNIORB_CONFIG must be set to name of the directory
# containing this file.
# Alternatively, and more cleanly, specify the Naming service with a
# corbaname URI:
InitRef = NameService=corbaloc:iiop:localhost:port-id/NameService
\endcode
replacing <tt>port-id</tt> by your port id computed above.
File <tt>omniORG.cfg</tt> contains the address of the name server. The name server and each Corba object you launch thus needs to access to this file. For that, define the following environment variable:
\code
setenv OMNIORB_CONFIG ${HOME}/omniORB/omniORB.cfg
\endcode
You need also to define a directory where log messages will be saved:
\code
setenv OMNINAMES_LOGDIR ${HOME}/omniORB/log
\endcode
You can eventually launch the name server:
\code
omniNames -start port-id
\endcode
the fisrt time and
\code
omniNames
\endcode
the following times.
\subsubsection hppDoc_python Controlling an application using python scripts
To send request to hppCorbaServer using python, you need to install packages
\li <tt>omniORBpy</tt> through robotpkg (<tt>cd ${HOME}/openrobots/robotpkg/devel/omniORBpy; make update</tt>) and
\li <tt>hppPython</tt> (either through <tt>robotpkg</tt> or through <tt>git</tt>).
Installation of \c hppPython is similar to other packages, except that command
\code
[package] libtoolize -c
\endcode
should be omitted.
<tt>hppPython</tt> installs a python script \c hppInit.py that initializes 3 CORBA clients, one to each the 3 CORBA objects instanciated by <tt>hppCorbaServer</tt> and described at the beginning of this section (\ref hppDoc_omniORB).
\c hppInit.py is installed in <tt>${HOME}/devel/share/script/python</tt>. Tu use this script, you need to set the following environment variables.
\code
setenv PYTHONPATH ${HOME}/devel/share/script/python:${HOME}/openrobots/lib/pythonx.y/site-packages:${PYTHONPATH}
setenv LD_LIBRARY_PATH ${HOME}/openrobots/lib:${LD_LIBRARY_PATH}
\endcode
where \c x.y is the version number of python installed by \c robotpkg.
Then open a python shell and call the initialization script
\code
[~] python
Python 2.4.3 (#1, Oct 23 2006, 14:19:47)
[GCC 4.1.1 20060525 (Red Hat 4.1.1-1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from hppInit import *
>>>
\endcode
Three variables \c robotClient, \c obstacleClient and \c problemClient are defined and can be used to send requests to \c hppCorbaServer.
For an example, see <tt>hppPython/src/example.py</tt>.
\section hppDoc_install_example Example: Intalling and running walk planner package and corresponding kpp Interface
\subsection hppDoc_install_example_subsec Installing necessary packages
Download, install <tt>robotpkg</tt> and type <tt>make update</tt> in <tt>graphics/kpp-interfacewalk</tt> on a machine with a Kineo license.
\subsection hppDoc_subsec_openhrp Starting OpenHRP
To download the model of HRP2, you need to launch OpenHRP.
\subsubsection hppDoc_subsubsec_nameServer Starting the name server
The first step consists in starting the name server:
\code
[~] ${HOME}/openrobots/bin/omniNames -start port-id
\endcode
where port-id is your port-id. If you run omniNames later, you will not need to specify again the port id. Simply type:
\code
[~] ${HOME}/openrobots/bin/omniNames
\endcode
\subsubsection hppDoc_subsubsec_modelloader Starting OpenHRP model loader
Type
\code
[~] ${HOME}/openrobots/bin/Modelloader.sh
ready
\endcode
\subsubsection hppDoc_subsubsec_run_kpp Running KineoPathPlanner with kppInterfaceWalk module
You can now run KineoPathPlanner and load kppInterfaceWalk module:
\code
${HOME}/openrobots/bin/KineoPathPlanner -ModulePath ${HOME}/openrobots/lib/modules/${HOST}/libkppInterfaceWalk.so
\endcode
where <tt>${HOST}</tt> is the name of your machine (or localhost.localdomain).
You should get the following interface:
\image html "kppInterfaceWalk.png" KineoPathPlanner with module kppInterfaceWalk.
\subsubsection hppDoc_solve_GUI Defining and solving a problem using menu "HPP/WALK"
To solve a simple problem without obstacles, do the following step:
\li Click in menu "HPP/WALK -> Load HRP2"
\li Click on button "Zoom All" and you should see the robot in a bounding box.
\li In the top left "Scene" tree, in the "Devices" node, select "Humanoid Box"
\li Click in menu "Insert -> Path -> New"
\li The background becomes grey. Move the box to the desired goal configuration.
\li Click on the button "Done" (top right panel). The background becomes blue again.
\li Click in menu "HPP/WALK -> Set Init and Goal Configs"
\li Click in menu "HPP/WALK -> Solve Problem"
\li In the "Scene" tree, in the "Paths" node, select the last path
\li Play the path in the path player that has appeared.
\subsubsection hppDoc_solve_python Defining and solving a problem using python scripts
\li Click in menu "HPP/WALK -> Start CORBA Server"
\li Open a python interpreter and type the commands below.
\code
[~] python
Python 2.4.3 (#1, Oct 23 2006, 14:19:47)
[GCC 4.1.1 20060525 (Red Hat 4.1.1-1)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from hppWalkInit import *
>>> initWalkPlanner(-8, -8, 0, 8, 8, 0)
>>> problemClient.solve()
\endcode
\li In the "Scene" tree, in the "Paths" node, select the last path
\li Play the path in the path player that has appeared.
You can of course add obstacles using Corba Client obstacleClient
*/
/**
\mainpage
\section hppDoc_sec_intro Introduction
HPP (Humanoid Path Planner) is a collection of software packages
implementing path planning functionalities for a humanoid robot.
HPP is built upon KineoWorks (KWS) and KineoPathPlanner-SDK (KPP-SDK).
It can be extended to path planning for other types of robots like digital actors for instance.
\section sec_organization Organization of the packages
The code relative to the HPP is distributed into several software packages. The packages are distributed into three
categories:
\htmlonly
<ul>
<li> <a href="algorithms.html">Algorithms</a> implementing path planning functionalities </li>
<li> <a href=corba.html"Corba</a> communication facilities to control the algorithms </li>
<li> <a href="kppInterface.html">Add-on</a> to KineoPathPlanner GUI interface. </li>
</ul>
*/
......@@ -20,9 +20,10 @@ It can be extended to path planning for other types of robots like digital actor
The code relative to the HPP is distributed into several software packages. The packages are distributed into three
categories:
<ul>
<li> <a href="@prefix@/share/doc/hppDoc/algorithm.html">Algorithms</a> implementing path planning functionalities </li>
<li> <a href="@prefix@/share/doc/hppDoc/corba.html">Corba</a> communication facilities to control the algorithms </li>
<li> <a href="@prefix@/share/doc/hppDoc/kppInterface.html">Add-on</a> to KineoPathPlanner GUI interface. </li>
<li> <a href="algorithm.html">Algorithms</a> implementing path planning functionalities </li>
<li> <a href="corba.html">Corba</a> communication facilities to control the algorithms </li>
<li> <a href="kppInterface.html">Add-on</a> to KineoPathPlanner GUI interface. </li>
<li> <a href="roboticComponent.html">Robotic components</a> including HPP algorithms</li>
</ul>
<hr>
......
<HTML>
<HEAD>
<TITLE>Humanoid Path Planner Documentation</TITLE>
<LINK HREF="package.css" REL="stylesheet" TYPE="text/css">
</HEAD>
<BODY>
<h1>Robotic components</h1>
<h3>RTM Component specifications</h3>
<table>
<tr><td width="20"></td><td><a href="@HPPWALKPLANNERCOMPSPEC_DOCDIR@/main.html">hppWalkPlannerCompSpec</a>: </td><td> Services provided by humanoid walk planning component</td></tr>
</table>
<h3>GenoM components</h3>
<table>
<tr><td width="20"></td><td>walk-genom:</td><td>humanoid walk planning component</td></tr>
</table>
<h3>OpenRTM-AIST components</h3>
<table>
<tr><td width="20"></td><td><a href="@WALKPLANNERORTM_DOCDIR@/main.html">walkPlannerOrtm</a>: </td><td>humanoid walk planning component</td></tr>
</table>
<h3>RTM Component clients</h3>
<table>
<tr><td width="20"></td><td><a href="@WALKPLANNERORTMCLIENT_DOCDIR@/main.html">walkPlannerOrtClient</a>: </td><td> Python client for hppWalkPlannerCompSpec</td></tr>
</table>
<p>
<table border="0" cellpadding="0" cellspacing="0">
<tr><td></td></tr>
</table>
<br><br>
<hr>
<center>
<img src="./pictures/footer.jpg" Height=100>
<br>Humanoid Path Planner documentation</br>
</center>
<hr>
</center>
</body>
</head>
......@@ -69,6 +69,7 @@
<p><img src="pictures/ftv2blank.png" alt="&nbsp;" width=16 height=22 /><img src="pictures/ftv2node.png" alt="o" width=16 height=22 /><img src="pictures/ftv2doc.png" alt="*" width=24 height=22 /><a class="el" href="@prefix@/share/doc/hppDoc/algorithm.html" target="basefrm">Algorithms</a></p>
<p><img src="pictures/ftv2blank.png" alt="&nbsp;" width=16 height=22 /><img src="pictures/ftv2lastnode.png" alt="\" width=16 height=22 /><img src="pictures/ftv2doc.png" alt="*" width=24 height=22 /><a class="el" href="@prefix@/share/doc/hppDoc/corba.html" target="basefrm">Corba interfaces</a></p>
<p><img src="pictures/ftv2blank.png" alt="&nbsp;" width=16 height=22 /><img src="pictures/ftv2lastnode.png" alt="\" width=16 height=22 /><img src="pictures/ftv2doc.png" alt="*" width=24 height=22 /><a class="el" href="@prefix@/share/doc/hppDoc/kppInterface.html" target="basefrm">KPP Add-on</a></p>
<p><img src="pictures/ftv2blank.png" alt="&nbsp;" width=16 height=22 /><img src="pictures/ftv2node.png" alt="o" width=16 height=22 /><img src="pictures/ftv2doc.png" alt="*" width=24 height=22 /><a class="el" href="@prefix@/share/doc/hppDoc/roboticComponent.html" target="basefrm">Robotic components</a></p>
</div>
<p><img src="pictures/ftv2plastnode.png" alt="\" width=16 height=22 onclick="toggleFolder('folder3', this)"/><img src="pictures/ftv2folderclosed.png" alt="+" width=24 height=22 onclick="toggleFolder('folder3', this)"/>How to</p>
<div id="folder3">
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment