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

Automatic conversion tool + urdf library w/ meshes #74

Open
wants to merge 34 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
08ef58b
add optparser
Simon-Steinmann Aug 21, 2020
1f77776
Test world generation
Simon-Steinmann Aug 21, 2020
ed2c886
add LineSet Collada fix, so there are no errors
Simon-Steinmann Aug 21, 2020
6c09cfc
Add motomann
Simon-Steinmann Aug 24, 2020
cad05ee
pep8 compliancy
Simon-Steinmann Aug 25, 2020
a8cdae1
fix for first time running
Simon-Steinmann Aug 25, 2020
da6fc0e
delete urdf data
Simon-Steinmann Aug 27, 2020
d471af3
parse input, output direcotries
Simon-Steinmann Aug 27, 2020
949aa57
include latest collada fix
Simon-Steinmann Aug 27, 2020
88a3167
gitignore cleaned
Simon-Steinmann Aug 27, 2020
f452237
pep8 cleanup
Simon-Steinmann Aug 28, 2020
972a499
revert to master
Simon-Steinmann Aug 28, 2020
33c354a
Merge branch 'master' into Simon-Steinmann-batch-conversion
Simon-Steinmann Aug 28, 2020
280a6e4
add batch_conversion changes back in
Simon-Steinmann Aug 28, 2020
3ab9905
change expected proto files
Simon-Steinmann Aug 28, 2020
4dfaf4f
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
c89b9e1
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
71646b3
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
1787e7c
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
9536839
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
0040788
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
b33b2d4
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
55e21ec
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
b3e4405
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
28490a8
Update batch_conversion.py
DavidMansolino Aug 28, 2020
e20c429
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
1778318
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
0c9e3e0
Update test.
DavidMansolino Aug 28, 2020
640afae
Merge branch 'Simon-Steinmann-batch-conversion' of https://github.com…
DavidMansolino Aug 28, 2020
bbdd0c2
Add execution flag.
DavidMansolino Aug 28, 2020
e347776
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
5a6d27a
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
34f1576
Update batch_conversion.py
Simon-Steinmann Aug 28, 2020
d2a7bfc
Detailed description
Simon-Steinmann Aug 28, 2020
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
254 changes: 254 additions & 0 deletions batch_conversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
#!/usr/bin/env python
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved

"""URDF files to Webots PROTO converter."""
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved

import optparse
import datetime
import os
import shutil
import json
import math
# from urdf2webots.importer import convert2urdf
import urdf2webots.importer as importer
from copy import deepcopy

# Set Override = True if you want to override ALL config settings with the
# value in the OverrideCfg. This can be usefull for testing functionality
# quickly, and then doing a slow optimized conversion
Override = False
OverrideCfg = {
'disableMeshOptimization': False
}

# This defines the default config. If a urdf model doesnt have a config, one
# will created after this. If the importer.py is updated, so should this to
# add the additional functionality-
default_config = {
# outFile will get constructed with protosPath + robotName + '.proto'
'robotName': None,
'normal': False,
'boxCollision': True,
'disableMeshOptimization': True,
'enableMultiFile': True,
'staticBase': True,
'toolSlot': None,
'initRotation': "1 0 0 -1.5708"
}


class batchConversion():
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
def __init__(self, sourcePath, outPath):
self.urdf2webots_path = os.path.dirname(os.path.abspath(__file__))
# create a unique path for a new batch-conversion
today = datetime.date.today()
todaystr = today.isoformat()
self.protoTargetDir = outPath + '/protos_' + todaystr
n = 2
while os.path.exists(self.protoTargetDir):
while os.path.exists(self.protoTargetDir + '_Nr-' + str(n)):
n += 1
self.protoTargetDir = self.protoTargetDir + '_Nr-' + str(n)

# Find all the urdf files, and create the corresponding proto filePaths
urdf_root_dir = sourcePath # 'automatic_conversion/urdf'
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
os.chdir(urdf_root_dir)
# Walk the tree.
self.urdf_files = [] # List of the full filepaths.
self.protosPaths = []
for root, directories, files in os.walk('./'):
for filename in files:
# Join the two strings in order to form the full filepath.
if filename.endswith(".urdf"):
filepath = os.path.join(root, filename)
filepath = filepath[1:]
self.urdf_files.append(urdf_root_dir + filepath)
self.protosPaths.append(self.protoTargetDir + os.path.dirname(filepath) + '/')
os.chdir(self.urdf2webots_path)

# Report dict we can print at the end
self.EndReportMessage = {
'updateConfigs': [],
'newConfigs': [],
'converted': [],
'failed': []
}

def update_and_convert(self):
# Make sure all configs exist and are up to date, then convert all URDF files
DavidMansolino marked this conversation as resolved.
Show resolved Hide resolved
self.check_all_configs()
self.convert_all_urdfs()
self.print_end_report()

def create_proto_dir(self):
# create a new unique directory for our conversions
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
print(self.protoTargetDir)
os.makedirs(self.protoTargetDir)

def replace_ExtraProjectPath(self):
# this copies the converted files and puts them in the "automatic_conversion/ExtraProjectTest" directory
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
# This path can be added to webots, so we can test each new conversion, without having to adjust the Path
# every time
destination = os.path.dirname(self.protoTargetDir) + '/ExtraProjectTest'
if os.path.isfile(destination) or os.path.islink(destination):
os.remove(destination) # remove the file
elif os.path.isdir(destination):
shutil.rmtree(destination) # remove dir and all contains
shutil.copytree(self.protoTargetDir, destination)

def update_config(self, configFile, config=None):
# makes sure the existing configs are in the same format as the default. Existing options are conserved
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
new_config = deepcopy(default_config)
for key in new_config.keys():
try:
new_config[key] = config[key]
except:
pass
with open(configFile, 'w') as outfile:
json.dump(new_config, outfile, indent=4, sort_keys=True)

def check_all_configs(self):
# makes sure ever URDF file has a config and it is up do date
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
print('Checking config files...')
print('---------------------------------------')
for i in range(len(self.urdf_files)):
configFile = os.path.splitext(self.urdf_files[i])[0] + '.json'
try:
with open(configFile) as json_file:
config = json.load(json_file)
if config.keys() != default_config.keys():
print('Updating config (old settings will be carried over) - ', configFile)
self.EndReportMessage['updateConfigs'].append(configFile)
update_config(configFile, config)
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
else:
print('Config up to date - ', configFile)
except:
print('Generating new config for - ' + os.path.splitext(self.urdf_files[i])[0])
self.EndReportMessage['newConfigs'].append(configFile)
update_config(configFile)
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved

def convert_all_urdfs(self):
# convertes all URDF files according to their .json config files
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
self.create_proto_dir()
print('---------------------------------------')
print('Converting URDF to PROTO...')
print('---------------------------------------')
print('---------------------------------------')
for i in range(len(self.urdf_files)):
# clears any previous Geometry and Material references, so there is no conflict
importer.urdf2webots.parserURDF.Geometry.reference = {}
importer.urdf2webots.parserURDF.Material.namedMaterial = {}
configFile = os.path.splitext(self.urdf_files[i])[0] + '.json'
print('---------------------------------------')
print('Converting: ', self.urdf_files[i])
print('---------------------------------------')
try:
with open(configFile) as json_file:
config = json.load(json_file)
importer.convert2urdf(
inFile=self.urdf_files[i],
outFile=self.protosPaths[i] if not config['robotName'] else self.protosPaths[i] + config['robotName'] + '.proto',
normal=config['normal'],
boxCollision=config['boxCollision'],
disableMeshOptimization=OverrideCfg['disableMeshOptimization'] if Override else config['disableMeshOptimization'],
enableMultiFile=config['enableMultiFile'],
staticBase=config['staticBase'],
toolSlot=config['toolSlot'],
initRotation=config['initRotation'])
self.EndReportMessage['converted'].append(self.urdf_files[i])
except Exception as e:
print(e)
self.EndReportMessage['failed'].append(self.urdf_files[i])
print('---------------------------------------')

def print_end_report(self):
# print the Report
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
print('---------------------------------------')
print('------------End Report-----------------')
print('---------------------------------------')
print('Configs updated:')
print('---------------------------------------')
for config in self.EndReportMessage['updateConfigs']:
print(config)
print('---------------------------------------')
print('Configs created:')
print('---------------------------------------')
for config in self.EndReportMessage['newConfigs']:
print(config)
print('---------------------------------------')
print('Successfully converted URDF files:')
print('---------------------------------------')
for urdf in self.EndReportMessage['converted']:
print(urdf)
if len(self.EndReportMessage['failed']) > 0:
print('---------------------------------------')
print('Failedd URDF conversions:')
print('---------------------------------------')
for urdf in self.EndReportMessage['failed']:
print(urdf)

def create_test_world(self, spacing=3):
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
n_models = len(self.urdf_files)
n_row = math.ceil(n_models ** 0.5) # round up the sqrt of the number of models
grid_size = spacing * (n_row + 1)
projectDir = os.path.dirname(self.protoTargetDir) + '/ExtraProjectTest/TestAllRobots.wbt'
worldFile = open(projectDir, 'w')
worldFile.write('#VRML_SIM R2020b utf8\n')
worldFile.write('\n')
worldFile.write('WorldInfo {\n')
worldFile.write(' basicTimeStep 16\n')
worldFile.write(' coordinateSystem "NUE"\n')
worldFile.write('}\n')
worldFile.write('Viewpoint {\n')
worldFile.write(' orientation 0.7180951961816571 -0.6372947429425837 -0.27963315225232777 5.235063704863283\n')
worldFile.write(' position 1.9410928989638234 2.447392518518642 1.7311802992777219\n')
worldFile.write('}\n')
worldFile.write('TexturedBackground {\n')
worldFile.write('}\n')
worldFile.write('TexturedBackgroundLight {\n')
worldFile.write('}\n')
worldFile.write('RectangleArena {\n')
worldFile.write(' floorSize ' + str(grid_size) + ' ' + str(grid_size) + '\n')
worldFile.write(' floorTileSize 0.25 0.25\n')
worldFile.write(' wallHeight 0.05\n')
worldFile.write('}\n')
filenames = [] # List which will store all of the full filepaths.
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
row = 0
column = 1
for root, directories, files in os.walk(os.path.dirname(projectDir)):
for filename in files:
# Join the two strings in order to form the full filepath.
if filename.endswith(".proto"):
filepath = os.path.join(root, filename)
if os.path.dirname(filepath).split('_')[-1] != 'meshes':
name = os.path.splitext(os.path.basename(filename))[0]
if row == n_row:
column += 1
row = 0
row += 1
# add the model to the world file with translation to be spaced in a grid
worldFile.write(name + ' {\n')
worldFile.write(' translation ' + str(column * spacing - grid_size / 2) + ' 0 ' + str(row * spacing - grid_size / 2) + '\n')
worldFile.write('}\n')
worldFile.close()
os.chdir(self.urdf2webots_path)


if __name__ == '__main__':
optParser = optparse.OptionParser(usage='usage: %prog [options]')
optParser.add_option('--input', dest='sourcePath', default='', help='Specifies the urdf file to convert.')
optParser.add_option('--output', dest='outPath', 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.')
optParser.add_option('--force-mesh-optimization', dest='forceMesh', action='store_true', default=False, help='Set if mesh-optimization should be turned on for all conversions. This will take much longer!')
optParser.add_option('--no-project-override', dest='extraProj', action='store_true', default=False, help='Set if new conversions should NOT replace existing ones in "automatic_conversion/ExtraProjectTest".')
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
optParser.add_option('--update-cfg', dest='cfgUpdate', action='store_true', default=False, help='No conversion. Only updates or creates .json config for every URDF file in "automatic_conversion/urdf".')

options, args = optParser.parse_args()
bc = batchConversion(options.sourcePath, options.outPath)
Simon-Steinmann marked this conversation as resolved.
Show resolved Hide resolved
Override = options.forceMesh
if options.cfgUpdate:
bc.check_all_configs()
else:
bc.update_and_convert()
if not options.extraProj:
bc.replace_ExtraProjectPath()
bc.create_test_world()
2 changes: 1 addition & 1 deletion tests/expected/Human.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# This is a proto file for Webots for the Human
# Extracted from: /home/david/urdf2webots/tests/sources/gait2392_simbody/urdf/human.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/gait2392_simbody/urdf/human.urdf

PROTO Human [
field SFVec3f translation 0 0 0
Expand Down
2 changes: 1 addition & 1 deletion tests/expected/MotomanSia20d.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# This is a proto file for Webots for the MotomanSia20d
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d [
field SFVec3f translation 0 0 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_AXIS_BMesh [
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_AXIS_EMesh [
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_AXIS_LMesh [
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_AXIS_RMesh [
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_AXIS_SMesh [
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_AXIS_TMesh [
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_AXIS_UMesh [
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# license: Apache License 2.0
# license url: http://www.apache.org/licenses/LICENSE-2.0
# tags: hidden
# Extracted from: /home/david/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf
# Extracted from: /home/travis/urdf2webots/tests/sources/motoman/motoman_sia20d_support/urdf/sia20d.urdf

PROTO MotomanSia20d_MOTOMAN_BASEMesh [
]
Expand Down
11 changes: 5 additions & 6 deletions urdf2webots/writeProto.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,10 +155,9 @@ def URDFLink(proto, link, level, parentList, childList, linkList, jointList, sen
proto.write((level + 1) * indent + 'physics Physics {\n')
proto.write((level + 2) * indent + 'density -1\n')
proto.write((level + 2) * indent + 'mass %lf\n' % link.inertia.mass)
if link.inertia.position != [0.0, 0.0, 0.0]:
proto.write((level + 2) * indent + 'centerOfMass [ %lf %lf %lf ]\n' % (link.inertia.position[0],
link.inertia.position[1],
link.inertia.position[2]))
proto.write((level + 2) * indent + 'centerOfMass [ %lf %lf %lf ]\n' % (link.inertia.position[0],
link.inertia.position[1],
link.inertia.position[2]))
if link.inertia.ixx > 0.0 and link.inertia.iyy > 0.0 and link.inertia.izz > 0.0:
i = link.inertia
inertiaMatrix = [i.ixx, i.ixy, i.ixz, i.ixy, i.iyy, i.iyz, i.ixz, i.iyz, i.izz]
Expand Down Expand Up @@ -503,8 +502,8 @@ def URDFShape(proto, link, level, normal=False):
if name is None:
if visualNode.geometry.name is not None:
name = computeDefName(visualNode.geometry.name)
if visualNode.geometry.defName is None:
name = robotNameMain + '_' + name if robotNameMain else name
name = robotNameMain + '_' + name if robotNameMain else name
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have to define the name before the condition, otherwise the adjusted path is not used, causing errors

if visualNode.geometry.defName is None:
print('Create meshFile: %sMesh.proto' % name)
filepath = '%s/%sMesh.proto' % (meshFilesPath, name)
meshProtoFile = open(filepath, 'w')
Expand Down