Skip to content

Commit

Permalink
Add relative paths prefix when converting from URDF content (#159)
Browse files Browse the repository at this point in the history
* add relative paths prefix

* fix regex and test and prepare release

* Apply suggestions from code review

Co-authored-by: Olivier Michel <Olivier.Michel@cyberbotics.com>

Co-authored-by: Olivier Michel <Olivier.Michel@cyberbotics.com>
  • Loading branch information
BenjaminHug8 and omichel authored Feb 15, 2022
1 parent 0ce4d72 commit 07b6822
Show file tree
Hide file tree
Showing 4 changed files with 34 additions and 21 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ The script accepts the following arguments:
- **--init-pos=JointPositions**: Set the initial positions of your robot joints. Example: `--init-pos="[1.2, 0.5, -1.5]"` would set the first 3 joints of your robot to the specified values, and leave the rest with their default value.
- **--link-to-def**: Creates a DEF with the link name for each solid to be able to access it using getFromProtoDef(defName) (for PROTO conversion only).
- **--joint-to-def**: Creates a DEF with the joint name for each joint to be able to access it using getFromProtoDef(defName) (for PROTO conversion only).
- **--relative-path-prefix**: If **--input** is not set, the relative paths in your URDF file sent through stdin will use this prefix. For example: `filename="head.obj"` with `--relative-path-prefix="/home/user/myRobot/"` will become `filename="/home/user/myRobot/head.obj"`.

In case the **--input** option is missing, the script will read the URDF content from `stdin`.
In that case, you can pipe the content of your URDF file into the script: `cat my_robot.urdf | urdf2proto.py`.
Relative paths present in your URDF file will be treated relatively to the current directory from which the script is called.
Relative paths present in your URDF file will be treated relatively to the current directory from which the script is called unless **--relative-path-prefix** is set.

> Previously the **--static-base** argument was supported in order to set the base link to be static (disabled physics). It has been removed as there is a better way to do it by adding the following to your URDF file (assuming **base_link** is the root link of your robot):
>
Expand Down Expand Up @@ -80,6 +81,7 @@ The command line arguments available from the terminal are also available from t
| --init-pos | initPos |
| --link-to-def | linkToDef |
| --joint-to-def | jointToDef |
| --relative-path-prefix | relativePathPrefix |
In Python, you can convert a URDF file by passing its path as an argument to the `convertUrdfFile()` function or directly by passing its content as an argument to the `convertUrdfContent()` function.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setuptools.setup(
name='urdf2webots',
version='1.0.15',
version='1.0.16',
author='Cyberbotics',
author_email='support@cyberbotics.com',
description='A converter between URDF and PROTO files.',
Expand Down
14 changes: 7 additions & 7 deletions tests/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
'input': pathlib.Path(human_file_path).read_text(),
'output': os.path.join(resultDirectory, 'Human.proto'),
'expected': [os.path.join(expectedDirectory, 'Human.proto')],
'arguments': ''
'relativePathPrefix': '/home/runner/work/urdf2webots/urdf2webots/tests/sources/gait2392_simbody/urdf',
}
]

Expand All @@ -58,7 +58,7 @@
]


def fileCompare(file1, file2, checkPaths=True):
def fileCompare(file1, file2):
"""Compare content of two files."""
with open(file1) as f1, open(file2) as f2:
for i, (line1, line2) in enumerate(zip(f1, f2)):
Expand All @@ -68,7 +68,7 @@ def fileCompare(file1, file2, checkPaths=True):
elif line1.startswith('#VRML_SIM') and line2.startswith('#VRML_SIM'):
# This line may differ according to Webots version used
continue
elif not checkPaths or ('CI' not in os.environ and '/home/runner/work/' in line2):
elif 'CI' not in os.environ and '/home/runner/work/' in line2:
# When testing locally, the paths may differ.
continue
elif line1 != line2:
Expand Down Expand Up @@ -103,19 +103,19 @@ def testInputContentOutputProto(self):
"""Test that urdf2webots produces an expected PROTO file using URDF content as input."""
print('Start tests with input "URDF content" and output "PROTO file"...')
for paths in modelContentProto:
convertUrdfContent(input=paths['input'], output=paths['output'])
convertUrdfContent(input=paths['input'], output=paths['output'], relativePathPrefix=paths['relativePathPrefix'])
for expected in paths['expected']:
self.assertTrue(fileCompare(expected.replace('expected', 'results'), expected, checkPaths=False),
self.assertTrue(fileCompare(expected.replace('expected', 'results'), expected),
msg='Expected result mismatch when exporting input to "%s"' % paths['output'])

def testInputContentStdinOutputProto(self):
"""Test that urdf2webots produces an expected PROTO file using URDF content in the stdin buffer as input."""
print('Start tests with input "URDF content" and output "PROTO file"...')
for paths in modelContentProto:
sys.stdin = io.StringIO(paths['input'])
convertUrdfFile(output=paths['output'])
convertUrdfFile(output=paths['output'], relativePathPrefix=paths['relativePathPrefix'])
for expected in paths['expected']:
self.assertTrue(fileCompare(expected.replace('expected', 'results'), expected, checkPaths=False),
self.assertTrue(fileCompare(expected.replace('expected', 'results'), expected),
msg='Expected result mismatch when exporting input to "%s"' % paths['output'])

def testInputFileOutputRobotString(self):
Expand Down
35 changes: 23 additions & 12 deletions urdf2webots/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ def mkdirSafe(directory):
print('Directory "' + directory + '" already exists!')


def convertUrdfFile(input = None, output=None, robotName=None, normal=False, boxCollision=False,
def convertUrdfFile(input=None, output=None, robotName=None, normal=False, boxCollision=False,
disableMeshOptimization=False, enableMultiFile=False,
toolSlot=None, initTranslation='0 0 0', initRotation='0 0 1 0',
initPos=None, linkToDef=False, jointToDef=False):
initPos=None, linkToDef=False, jointToDef=False, relativePathPrefix=None):
"""Convert a URDF file into a Webots PROTO file or Robot node string."""
urdfContent = None
if not input:
Expand Down Expand Up @@ -86,7 +86,7 @@ def convertUrdfFile(input = None, output=None, robotName=None, normal=False, box
return convertUrdfContent(urdfContent, output, robotName, normal, boxCollision,
disableMeshOptimization, enableMultiFile,
toolSlot, initTranslation, initRotation,
initPos, linkToDef, jointToDef)
initPos, linkToDef, jointToDef, relativePathPrefix)


convertUrdfFile.urdfPath = None
Expand All @@ -95,18 +95,23 @@ def convertUrdfFile(input = None, output=None, robotName=None, normal=False, box
def convertUrdfContent(input, output=None, robotName=None, normal=False, boxCollision=False,
disableMeshOptimization=False, enableMultiFile=False,
toolSlot=None, initTranslation='0 0 0', initRotation='0 0 1 0',
initPos=None, linkToDef=False, jointToDef=False):
initPos=None, linkToDef=False, jointToDef=False, relativePathPrefix=None):
"""
Convert a URDF content string into a Webots PROTO file or Robot node string.
The current working directory will be used for relative paths in your URDF file.
To use the location of your URDF file for relative paths, please use the convertUrdfFile() function.
"""
# Retrieve urdfPath if this function has been called from convertUrdfFile()
# And set urdfDirectory accordingly
urdfPath = None
if convertUrdfFile.urdfPath is not None:
urdfPath = convertUrdfFile.urdfPath
urdfDirectory = os.path.dirname(urdfPath)
convertUrdfFile.urdfPath = None
elif relativePathPrefix is not None:
urdfDirectory = relativePathPrefix
else:
urdfPath = os.getcwd()
urdfDirectory = os.getcwd()

if not type(initTranslation) == str or len(initTranslation.split()) != 3:
sys.exit('--translation argument is not valid. It has to be of Type = str and contain 3 values.')
Expand Down Expand Up @@ -147,9 +152,9 @@ def convertUrdfContent(input, output=None, robotName=None, normal=False, boxColl
urdf2webots.parserURDF.Geometry.reference.clear()

# Replace "package://(.*)" occurences
for match in re.finditer('"package://(.*)"', input):
for match in re.finditer('"package://(.*?)"', input):
packageName = match.group(1).split('/')[0]
directory = os.path.dirname(urdfPath)
directory = urdfDirectory
while packageName != os.path.split(directory)[1] and os.path.split(directory)[1]:
directory = os.path.dirname(directory)
if not os.path.split(directory)[1]:
Expand Down Expand Up @@ -228,7 +233,7 @@ def convertUrdfContent(input, output=None, robotName=None, normal=False, boxColl
rootLink = urdf2webots.parserURDF.Link()

for link in linkElementList:
linkList.append(urdf2webots.parserURDF.getLink(link, os.path.dirname(urdfPath)))
linkList.append(urdf2webots.parserURDF.getLink(link, urdfDirectory))
for joint in jointElementList:
jointList.append(urdf2webots.parserURDF.getJoint(joint))
if not isProto:
Expand Down Expand Up @@ -292,7 +297,8 @@ def convertUrdfContent(input, output=None, robotName=None, normal=False, boxColl
optParser = optparse.OptionParser(usage='usage: %prog --input=my_robot.urdf [options]')
optParser.add_option('--input', dest='input', default='', help='Specifies the URDF file.')
optParser.add_option('--output', dest='output', default='', help='Specifies the path and, if ending in ".proto", name '
'of the resulting PROTO file. The filename minus the .proto extension will be the robot name (for PROTO conversion only).')
'of the resulting PROTO file. The filename minus the .proto extension will be the robot name '
'(for PROTO conversion only).')
optParser.add_option('--robot-name', dest='robotName', default=None, help='Specifies the name of the robot '
'and generate a Robot node string instead of a PROTO file (has to be unique).')
optParser.add_option('--normal', dest='normal', action='store_true', default=False,
Expand All @@ -315,9 +321,14 @@ def convertUrdfContent(input, output=None, robotName=None, normal=False, boxColl
'set the first 3 joints of your robot to the specified values, and leave the rest with their '
'default value.')
optParser.add_option('--link-to-def', dest='linkToDef', action='store_true', default=False,
help='Creates a DEF with the link name for each solid to be able to access it using getFromProtoDef(defName) (for PROTO conversion only).')
help='Creates a DEF with the link name for each solid to be able to access it using getFromProtoDef(defName) '
'(for PROTO conversion only).')
optParser.add_option('--joint-to-def', dest='jointToDef', action='store_true', default=False,
help='Creates a DEF with the joint name for each joint to be able to access it using getFromProtoDef(defName) (for PROTO conversion only).')
help='Creates a DEF with the joint name for each joint to be able to access it using getFromProtoDef(defName) '
'(for PROTO conversion only).')
optParser.add_option('--relative-path-prefix', dest='relativePathPrefix', default=None,
help='If set and --input not specified, relative paths in your URDF file will be treated relatively to it '
'rather than relatively to the current directory from which the script is called.')
options, args = optParser.parse_args()
convertUrdfFile(options.input, options.output, options.robotName, options.normal, options.boxCollision, options.disableMeshOptimization,
options.enableMultiFile, options.toolSlot, options.initTranslation, options.initRotation, options.initPos, options.linkToDef, options.jointToDef)
options.enableMultiFile, options.toolSlot, options.initTranslation, options.initRotation, options.initPos, options.linkToDef, options.jointToDef, options.relativePathPrefix)

0 comments on commit 07b6822

Please sign in to comment.