Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for loading URDF models in MORSE #788

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*.so
*.pyc
*~
build/
build*/
CMakeFiles/
CMakeCache.txt
install_manifest.txt
Expand Down
2 changes: 1 addition & 1 deletion doc/conf.py.in
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ sys.path.append('@CMAKE_CURRENT_SOURCE_DIR@/doc/exts')
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.

extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.todo', 'sphinx.ext.viewcode', 'vimeo','gallery', 'tag', 'examples']
extensions.append('sphinx.ext.pngmath')
extensions.append('sphinx.ext.imgmath')

# Add any paths that contain templates here, relative to this directory.
templates_path = ['@CMAKE_CURRENT_SOURCE_DIR@/doc/morse-theme/templates', '@CMAKE_CURRENT_SOURCE_DIR@/doc/sphinxext']
Expand Down
5 changes: 5 additions & 0 deletions doc/morse/components_library.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ Robots and robotic bases
user/robots/*


.. note::

You can also load your own URDF robot description. :doc:`Click here for
details<user/urdf>`.

Sensors
+++++++

Expand Down
16 changes: 11 additions & 5 deletions doc/morse/quickstart.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ So, what happened here?
`morse create mysim` actually created a new subfolder in the current directory.

If you inspect it, you will find a Python file called `default.py` and two
subfolders. `default.py` is what we call a **Builder script** (because it uses MORSE's :doc:`Builder API<user/builder>`) which describes your simulation.
subfolders. `default.py` is what we call a **Builder script** (because it uses
MORSE's :doc:`Builder API<user/builder>`) which describes your simulation.

.. note::
Note that the script is still Python: besides the Builder API, you can use any
Expand Down Expand Up @@ -66,12 +67,17 @@ indicated in the output. You will see that by default, your custom robot has the
same two actuators and sensor as in `default.py`. You can change them to build
a robot that matches your needs.

Soo also `morse add actuator` and `morse add sensor` if you need to create
.. note::

If you already have a URDF file for your robot, :doc:`check this page<user/urdf>` to learn how
to use import it.

You can also use `morse add actuator` and `morse add sensor` to create
custom actuators/sensors.

That's all folks! You now know the basics of MORSE. Head to the
:doc:`tutorials` section to learn how to interact with your simulated robots
and for more advanced examples.
That's all folks! You now know the basics of MORSE. Head to the :doc:`tutorials`
section to learn how to interact with your simulated robots and for more
advanced examples.



Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,6 @@
A Journey to a New Simulation
=============================


.. warning::

This tutorial requires a recent version of MORSE. Follow the instructions
:doc:`here <../installation>` to install the latest version.


.. note::
This tutorial will guide you through the development of a complete
simulation, including the creation of a robot and new actuators from
Expand Down
11 changes: 10 additions & 1 deletion doc/morse/user/builder.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ scene:
.. MORSE Builder empty


Since there is no robot yet, you won't be able launch the simulation.
Since there is no robot yet, you will not be able launch the simulation.

The environment comes from the file ``data/environments/indoors-1/indoor-1.blend``,
you could also tell MORSE to load an environment from any location on your system
Expand Down Expand Up @@ -144,6 +144,15 @@ Let's add a robot to our scene:

You should see the ``ATRV`` at the center of the scene.

.. note::

Here, we are using one of the pre-built robot (ATRV). MORSE comes with
several such :doc:`preconfigured robots<../components_library>`. You can also
load your own :doc:`URDF file<urdf>`, or a custom robot model built
directly in Blender (see a :doc:`tutorial
here<advanced_tutorials/a_journey_to_a_new_simulation>`).


.. warning::

Loop handling in builder scripts (e.g., to create multiple
Expand Down
50 changes: 50 additions & 0 deletions doc/morse/user/urdf.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
Using URDF robot description
============================

.. warning::

URDF support is currently considered experimental. Please report any issue
you encounter on `the MORSE issue
tracker <https://github.com/morse-simulator/morse/issues>`_.

`URDF <http://wiki.ros.org/urdf>`_ is a standard XML format used to describe
robots. You can load URDF files directly from MORSE :doc:`builder
script<user/builder>` by simply passing a path to a URDF file to a robot
constructor:

.. code-block:: python

from morse.builder import *

# Add robot from its URDF description
robot = Robot("path/to/my/robot.urdf", name="MyRobot")

# then, standard BUilder script. For instance:

# Append an actuator to the robot
motion = MotionVW()
robot.append(motion)

# Append a sensor to the robot
pose = Pose()
pose.translate(z = 0.75)
robot.append(pose)

# Configure the robot on the 'socket' interface
robot.add_default_interface('socket')

env = Environment('indoors-1/indoor-1')


MORSE will automatically import the Collada/STL meshes referenced in your URDF
file.

.. warning::

Often, the URDF file references meshes using the ROS specific `package://`
protocol. In this case, MORSE attempts to find the meshes by replacing
`package://` by the first entry in your `$ROS_PACKAGE_PATH` environment
variable. If MORSE fails to find the meshes, check your `$ROS_PACKAGE_PATH`.

Alternatively, provide full paths to your meshes in your URDF file using the
`file://` protocol.
1 change: 1 addition & 0 deletions src/morse/builder/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
from .robots import *
from .actuators import *
from .sensors import *
from .urdf import *
from morse.core.morse_time import TimeStrategies
28 changes: 24 additions & 4 deletions src/morse/builder/abstractcomponent.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,9 @@ class AbstractComponent(object):

def __init__(self, obj=None, filename='', category=''):
self.set_blender_object(obj)
self._blender_filename = filename # filename for datastream configuration
self._resource_filename = filename # filename for datastream configuration
self.has_urdf = True if self._resource_filename.endswith(".urdf") else False

self._category = category # for morseable
self.basename = None
self.children = []
Expand Down Expand Up @@ -647,6 +649,20 @@ def _compute_filepath(self, component):

return filepath

def load_urdf(self):
"""
Loads the URDF file passed to the AbstractComponent constructor.

:return: the root object created from the URDF file.
"""
if not self.has_urdf:
return []

from morse.builder.urdf import URDF

self.urdf = URDF(self._resource_filename)
return self.urdf.build()

def append_meshes(self, objects=None, component=None, prefix=None):
""" Append the component's Blender objects to the scene

Expand All @@ -671,7 +687,11 @@ def append_meshes(self, objects=None, component=None, prefix=None):
:return: list of the imported (selected) Blender objects
"""

component = component or self._blender_filename
if component is None and self.has_urdf:
logger.warning("calling AbstractComponent.append_meshes with a URDF component. Skipping it.")
return []

component = component or self._resource_filename

if not component: # no Blender resource: simply create an empty
bpymorse.deselect_all()
Expand Down Expand Up @@ -712,7 +732,7 @@ def append_meshes(self, objects=None, component=None, prefix=None):
return bpymorse.get_selected_objects()

def append_scenes(self, component=None):
component = component or self._blender_filename
component = component or self._resource_filename

filepath = self._compute_filepath(component)

Expand All @@ -737,7 +757,7 @@ def append_collada(self, component=None):
:return: list of the imported Blender objects
"""
if not component:
component = self._blender_filename
component = self._resource_filename

if component.endswith('.dae'):
filepath = os.path.abspath(component) # external blend file
Expand Down
15 changes: 14 additions & 1 deletion src/morse/builder/bpymorse.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ def empty_method(*args, **kwargs):
link = empty_method # 2.71.6
append = empty_method # 2.71.6
collada_import = empty_method
stl_import = empty_method
add_object = empty_method
add_empty = empty_method
mode_set = empty_method
new_mesh = empty_method
new_object = empty_method
apply_transform = empty_method
Expand All @@ -48,6 +50,7 @@ def empty_method(*args, **kwargs):
del_scene = empty_method
armatures = empty_method
make_links_scene = empty_method
origin_set = empty_method

if bpy:
select_all = bpy.ops.object.select_all
Expand All @@ -72,8 +75,10 @@ def empty_method(*args, **kwargs):
else: # link_append dropped in 2.71.6
link_append = bpy.ops.wm.link_append
collada_import = bpy.ops.wm.collada_import
stl_import = bpy.ops.import_mesh.stl
add_object = bpy.ops.object.add
add_empty = bpy.ops.object.empty_add
mode_set = bpy.ops.object.mode_set
new_mesh = bpy.data.meshes.new
new_object = bpy.data.objects.new
apply_transform = bpy.ops.object.transform_apply
Expand All @@ -82,6 +87,7 @@ def empty_method(*args, **kwargs):
del_scene = bpy.ops.scene.delete
armatures = bpy.data.armatures
make_links_scene = bpy.ops.object.make_links_scene
origin_set = bpy.ops.object.origin_set

def version():
if bpy:
Expand Down Expand Up @@ -110,6 +116,13 @@ def get_first_selected_object():
else:
return None

def active_object():
if bpy:
return bpy.context.active_object
else:
return None


def get_selected_objects():
if bpy:
return bpy.context.selected_objects
Expand Down Expand Up @@ -139,7 +152,7 @@ def get_materials():

def get_material(name_or_id):
if bpy and bpy.data.materials:
return bpy.data.materials[name_or_id]
return bpy.data.materials.get(name_or_id)
else:
return None

Expand Down
28 changes: 18 additions & 10 deletions src/morse/builder/morsebuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,9 @@ def __init__(self, category='', filename='',blender_object_name=None, make_morse
:param category: The category of the component (folder in
MORSE_COMPONENTS)
:param filename: The name of the component (file in
MORSE_COMPONENTS/category/name.blend) If ends with '.blend',
append the objects from the Blender file.
MORSE_COMPONENTS/category/name.{blend, urdf}).
If it ends with '.blend', append the objects from the Blender
file. If it ends with '.urdf', it attemps to laod the URDF file.
:param blender_object_name: If set, use the given Blender object
as 'root' for this component. Otherwise, select the first
available Blender object (the top parent in case of a hierarchy
Expand All @@ -137,14 +138,20 @@ def __init__(self, category='', filename='',blender_object_name=None, make_morse
simulation, append default Morse ones. See self.morseable()
"""
AbstractComponent.__init__(self, filename=filename, category=category)
imported_objects = self.append_meshes()

if blender_object_name is None:
# Here we use the fact that after appending, Blender select the objects
# and the root (parent) object first ( [0] )
self.set_blender_object(imported_objects[0])
if self.has_urdf:
root = self.load_urdf()
self.set_blender_object(root)

else:
self.set_blender_object([o for o in imported_objects if o.name == blender_object_name][0])
imported_objects = self.append_meshes()

if blender_object_name is None:
# Here we use the fact that after appending, Blender select the objects
# and the root (parent) object first ( [0] )
self.set_blender_object(imported_objects[0])
else:
self.set_blender_object([o for o in imported_objects if o.name == blender_object_name][0])

# If the object has no MORSE logic, add default one
if make_morseable and category in ['sensors', 'actuators', 'robots'] \
Expand All @@ -157,8 +164,9 @@ def __init__(self, filename='', name=None, blender_object_name=None):
""" Initialize a MORSE robot

:param filename: The name of the component (file in
MORSE_COMPONENTS/category/name.blend) If ends with '.blend',
append the objects from the Blender file.
MORSE_COMPONENTS/category/name.{blend, urdf}).
If it ends with '.blend', append the objects from the Blender
file. If it ends with '.urdf', it attemps to laod the URDF file.
:param name: Name of the resulting robot in the simulation, default to 'robot'.
:param blender_object_name: If set, use the given Blender object
in 'filename' as 'root' for this robot. Otherwise, select the first
Expand Down
Loading