Merge branch '6-model-config-xml' into 'master'

Gazebo Export Package restucturing

Closes #5 and #6

See merge request robosphere/forks/ARBench!2
This commit is contained in:
Igor Brylyov 2022-02-07 20:54:17 +00:00
commit 8241627436
5 changed files with 186 additions and 91 deletions

View file

@ -394,6 +394,16 @@ def exportPartInfoAndFeaturesDialogue():
+ str(unique_selected[0].Label) + str(unique_selected[0].Label)
+ " exported to " + str(ofile) + "\n") + " exported to " + str(ofile) + "\n")
def exportGazeboModels():
import GazeboExport
doc = FreeCAD.activeDocument()
for obj in doc.Objects:
"""Export solid shapes."""
if (isinstance(obj.Shape, Part.Solid) if hasattr(obj, 'Shape') else False):
GazeboExport.export_gazebo_model(obj, os.path.split(doc.FileName)[0], configs={})
elif isinstance(obj, Part.Feature):
FreeCAD.Console.PrintMessage('{0} part is not valid. It has a Compound type, but Solids there are hidden. Please convert it to single Solid'.format(obj.Label))
################################################################### ###################################################################
# GUI Commands # GUI Commands
@ -401,12 +411,19 @@ def exportPartInfoAndFeaturesDialogue():
uidir = os.path.join(FreeCAD.getUserAppDataDir(), uidir = os.path.join(FreeCAD.getUserAppDataDir(),
"Mod", __workbenchname__, "UI") "Mod", __workbenchname__, "UI")
icondir = os.path.join(uidir, "icons") icondir = os.path.join(uidir, "icons")
spawnClassCommand("ExportPartInfoAndFeaturesDialogueCommand", spawnClassCommand("ExportPartInfoAndFeaturesDialogueCommand",
exportPartInfoAndFeaturesDialogue, exportPartInfoAndFeaturesDialogue,
{"Pixmap": str(os.path.join(icondir, "parttojson.svg")), {"Pixmap": str(os.path.join(icondir, "parttojson.svg")),
"MenuText": "Export info and featureframes", "MenuText": "Export info and featureframes",
"ToolTip": "Export part properties (placement, C.O.M) and feature frames"}) "ToolTip": "Export part properties (placement, C.O.M) and feature frames"})
spawnClassCommand("ExportGazeboModels",
exportGazeboModels,
{"Pixmap": str(os.path.join(icondir, "gazeboexport.svg")),
"MenuText": "Export SDF-models to Gazebo",
"ToolTip": "Export SDF-models for all solid parts"})
################################################################### ###################################################################
# Information from primitive type # Information from primitive type

View file

@ -5,17 +5,14 @@ import collada
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
from xml.dom.minidom import parseString from xml.dom.minidom import parseString
from math import radians as _radians from math import radians as _radians
import Part
def export_gazebo_model(obj, export_dir, configs={}):
name = obj.Label
def export_gazebo_model(model_dir, configs={}):
doc = FreeCAD.activeDocument()
assembly_dir = os.path.split(doc.FileName)[0]
scale = configs.get('scale', 0.001) scale = configs.get('scale', 0.001)
scale_vec = FreeCAD.Vector([scale]*3) scale_vec = FreeCAD.Vector([scale]*3)
for obj in doc.Objects:
if obj.isDerivedFrom("Part::Feature"):
name = obj.Label
density = configs.get('density', 1000) density = configs.get('density', 1000)
bounding_box = obj.Shape.BoundBox bounding_box = obj.Shape.BoundBox
@ -40,12 +37,12 @@ def export_gazebo_model(model_dir, configs={}):
placement = shape.Placement placement = shape.Placement
placement.Base.scale(*scale_vec) placement.Base.scale(*scale_vec)
mesh_file = os.path.join(model_dir, name, 'meshes') model_dir = os.path.join(export_dir, name)
mesh_file = os.path.splitext(mesh_file)[0] + name + '.dae' mesh_dir = os.path.join(model_dir, 'meshes')
mesh_dir = os.path.split(mesh_file)[0] mesh_file = os.path.join(mesh_dir, name + '.dae')
os.makedirs(mesh_dir, exist_ok=True) os.makedirs(mesh_dir, exist_ok=True)
export_collada(doc, [obj], mesh_file, scale=scale, offset=com*-1) export_collada([obj], mesh_file, scale=scale, offset=com*-1)
pose = placement.copy() pose = placement.copy()
pose.Base = com pose.Base = com
@ -59,9 +56,7 @@ def export_gazebo_model(model_dir, configs={}):
mass=mass, mass=mass,
inertia=inertia) inertia=inertia)
package = configs.get('ros_package', name) mesh_uri = os.path.relpath(mesh_file, export_dir)
mesh_uri = os.path.join(package,
os.path.relpath(mesh_file, model_dir))
mesh_uri = os.path.normpath(mesh_uri) mesh_uri = os.path.normpath(mesh_uri)
visual = Visual(name=name+'_visual', mesh=mesh_uri) visual = Visual(name=name+'_visual', mesh=mesh_uri)
@ -74,9 +69,11 @@ def export_gazebo_model(model_dir, configs={}):
collision=collision) collision=collision)
model.links.append(link) model.links.append(link)
with open(os.path.join(model_dir, name+'.sdf'), 'w') as sdf_file: with open(os.path.join(model_dir, 'model.sdf'), 'w') as sdf_file:
sdf_file.write(model.to_xml_string('sdf')) sdf_file.write(model.to_xml_string('sdf'))
with open(os.path.join(model_dir, 'model.config'), 'w') as config_file:
config_file.write(config(name, 'model.sdf', 'Author', 'Email', 'Comment', 'Version'))
################################################################### ###################################################################
# Export helpers # Export helpers
@ -84,10 +81,9 @@ def export_gazebo_model(model_dir, configs={}):
def export_collada(doc, exportList, filename, scale=1, quality=1, offset=np.zeros(3)): def export_collada(exportList, filename, scale=1, quality=1, offset=np.zeros(3)):
'''FreeCAD collada exporter '''FreeCAD collada exporter
doc - FreeCAD document exportList - list of objects
exportList - list of objects from doc
scale - scaling factor for the mesh scale - scaling factor for the mesh
quality - mesh tessellation quality quality - mesh tessellation quality
offset - offset of the origin of the resulting mesh''' offset - offset of the origin of the resulting mesh'''
@ -218,6 +214,28 @@ def pose_xyz(pose):
xyz = pose.Base if hasattr(pose, 'Base') else pose xyz = pose.Base if hasattr(pose, 'Base') else pose
return ' '.join([flt2str(i) for i in xyz]) return ' '.join([flt2str(i) for i in xyz])
def config(model_name, sdf, author, email, desc, version):
top = ET.Element('model')
name = ET.SubElement(top, 'name')
name.text = model_name
ver = ET.SubElement(top, 'version')
ver.text = version
sdf_file = ET.SubElement(top, 'sdf')
sdf_file.text = sdf
sdf_file.set('version', '1.5')
author_tag = ET.SubElement(top, 'author')
author_name = ET.SubElement(author_tag, 'name')
author_name.text = author
email_address = ET.SubElement(author_tag, 'email')
email_address.text = email
description = ET.SubElement(top, 'description')
description.text = desc
dom = parseString(ET.tostring(top, encoding="unicode"))
return dom.toprettyxml(indent=' '*2)
class SpatialEntity(object): class SpatialEntity(object):
'''A base class for sdf/urdf elements containing name, pose and urdf_pose''' '''A base class for sdf/urdf elements containing name, pose and urdf_pose'''

View file

@ -14,7 +14,8 @@ class ARBench(Workbench):
self.framecommands = ["FrameCommand", self.framecommands = ["FrameCommand",
"AllPartFramesCommand", "AllPartFramesCommand",
"FeatureFrameCommand"] "FeatureFrameCommand"]
self.toolcommands = ["ExportPartInfoAndFeaturesDialogueCommand"] self.toolcommands = ["ExportPartInfoAndFeaturesDialogueCommand",
"ExportGazeboModels"]
self.appendToolbar("AR Frames", self.framecommands) self.appendToolbar("AR Frames", self.framecommands)
self.appendToolbar("AR Tools", self.toolcommands) self.appendToolbar("AR Tools", self.toolcommands)

View file

@ -1,5 +1,8 @@
![implementation preview](https://raw.githubusercontent.com/mahaarbo/ARBench/master/UI/icons/github_preview.png) ![implementation preview](https://raw.githubusercontent.com/mahaarbo/ARBench/master/UI/icons/github_preview.png)
# Arbench # Arbench
---___!!! USE WITH CAUTION! Plugin on heavy developement !!!___---
Annotation for robotics bench. A FreeCAD workbench for annotating frames of interest, exporting these w.r.t. the part frame, and exporting part information. Annotation for robotics bench. A FreeCAD workbench for annotating frames of interest, exporting these w.r.t. the part frame, and exporting part information.
# Installation instructions # Installation instructions
@ -19,6 +22,8 @@ This workbench supports versions of FreeCAD>0.16.
# Usage # Usage
## Export meta-data for part's feature frames
1. Click a small feature e.g. a circle 1. Click a small feature e.g. a circle
2. Press the feature frame creator (cone with a magnifying glass on it icon) 2. Press the feature frame creator (cone with a magnifying glass on it icon)
3. Chose type of feature to create 3. Chose type of feature to create
@ -29,41 +34,15 @@ This workbench supports versions of FreeCAD>0.16.
8. Use the json with whatever you want. E.g. [`arbench_part_publisher`](https://github.com/mahaarbo/arbench_part_publisher) 8. Use the json with whatever you want. E.g. [`arbench_part_publisher`](https://github.com/mahaarbo/arbench_part_publisher)
# Freecad to Gazebo exporter ## Generate part's model packages for Gazebo simulator
To generate SDF and URDF model from freecad assembly use python call: To generate SDF model packages from FreeCAD Document just press "Gazebo Export" button in ARBench UI. It will create folder for every `Solid` part in Document (`Compound` parts currently doesn't supported) with such structure
```python
freecad_exporter.export_gazebo_model(freecad_assembly_file, model_destination_folder, config)
``` ```
Note: Only links and joints are generated in the SDF model. To use the model with ros, use the URDF model. name_of_part
├── model.sdf
├── meshes
│ └── part.dae
└── model.config
## Config specification
```json
{
"name": "robot_name",
"joints_limits": {"upper": 90, "lower": -90, "effort": 10, "velocity": 5},
"transmission": {
"type": "transmission_interface/SimpleTransmission",
"hardware_interface": "hardware_interface/PositionJointInterface"
},
"joints_config": {
"type": "position_controllers/JointGroupPositionController",
"grouped": true
},
"joints_pid": {"p": 20.0, "i": 10.0, "d": 0.0, "i_clamp": 0.0},
"root_link": "base_link",
"ros_package": "humanoid_17dof_description",
"sdf_only": false,
"export": true
}
``` ```
This packages will placed by default in your FreeCAD Document's folder and could be moved to gazebo model's folder for using them in sumulator.
**sdf_only**: Export only SDF.
**export**: Export mesh files.
## Future plans
* Extend collada exporter to export materials from assemblies.
* Create a FreeCAD workbench to interactively assign joints and export to gazebo.
* Support any valid structures of assemblies.

80
UI/icons/gazeboexport.svg Normal file
View file

@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="300"
height="300"
id="svg6408"
version="1.1"
inkscape:version="0.48.4 r9939"
sodipodi:docname="New document 18">
<defs
id="defs6410" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="1.979899"
inkscape:cx="170.62581"
inkscape:cy="178.84029"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:window-width="1749"
inkscape:window-height="1203"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="0" />
<metadata
id="metadata6413">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-752.36218)">
<g
transform="matrix(1.7219458,0,0,1.7219458,-240.52546,490.39612)"
id="g6387">
<path
inkscape:connector-curvature="0"
id="path4004"
d="m 164.74024,205.99021 1.89902,67.41531 59.81923,38.92997 0.94951,-34.65717 35.13192,-19.46498 z"
style="fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
d="m 222.38424,244.55563 2.02975,1.25975 c 0.72813,0.45312 1.55275,0.67775 2.38038,0.67775 0.82574,0 1.65337,-0.22463 2.38337,-0.68113 l 2.03225,-1.26463 20.60538,12.75975 -27.51213,17.13288 c -1.32725,0.82362 -2.13288,2.27775 -2.1265,3.84512 l 0.0805,28.07713 -49.7045,-30.86425 49.8315,-30.94237 z m -54.03223,-33.54926 45.47998,28.23975 -45.47998,28.23825 0,-56.478 z m 125.63548,28.14788 c -0.004,-1.63325 -0.8925,-3.13962 -2.32625,-3.933 -1.43,-0.792 -3.17875,-0.74712 -4.5675,0.11712 l -26.73338,16.648 -20.60312,-12.75887 52.209,-32.51463 c 1.325,-0.82512 2.13125,-2.27537 2.13125,-3.83637 -0.004,-1.55763 -0.8125,-3.00838 -2.1375,-3.82963 l -62.79113,-38.88062 c -1.4575,-0.90375 -3.29824,-0.90125 -4.75487,0.002 l -62.79292,38.99137 c -1.3252,0.82175 -2.13233,2.272 -2.13233,3.83301 0,0.40574 0,72.09862 0,72.50487 0,1.56062 0.80713,3.01025 2.13233,3.8345 l 62.79292,38.98977 c 0.0307,0.019 0.0625,0.0303 0.0929,0.0478 0.0346,0.0186 0.0634,0.0449 0.0991,0.0659 0.0708,0.0376 0.1455,0.0664 0.21963,0.10254 0.0732,0.0376 0.14499,0.0737 0.22224,0.10645 0.11713,0.0474 0.23676,0.0889 0.35638,0.125 0.0709,0.0249 0.13925,0.0513 0.2115,0.0688 0.13425,0.041 0.27187,0.0625 0.40863,0.0884 0.0571,0.0117 0.11724,0.0254 0.17574,0.0288 0.19688,0.0303 0.3955,0.0439 0.59238,0.0439 l 0,0 c 0,0 0.001,0 0.002,0 0.40926,0 0.82188,-0.0576 1.21825,-0.16748 0.004,-0.004 0.006,-0.004 0.006,-0.004 0.15387,-0.041 0.30525,-0.10254 0.45412,-0.16113 0.0449,-0.0186 0.0927,-0.0298 0.13676,-0.0474 0.126,-0.0552 0.24262,-0.12891 0.36225,-0.19532 0.066,-0.0361 0.13574,-0.0649 0.20074,-0.10253 l 0.002,-0.003 c 0.003,0 0.003,0 0.003,0 l 0.12062,-0.0762 62.66538,-38.91018 c 1.33125,-0.82762 2.13625,-2.28175 2.13375,-3.84912 l -0.11,-36.32913"
id="path3115"
style="fill:#231f20;fill-opacity:1;fill-rule:nonzero;stroke:none"
inkscape:connector-curvature="0" />
<path
d="m 231.20499,280.76812 0.0772,25.623 53.784,-33.398 -0.075,-25.71625 -53.78625,33.49125"
id="path3117"
style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:none"
inkscape:connector-curvature="0" />
<path
d="m 172.55274,202.99313 54.23975,33.67824 54.23925,-33.77687 -54.23437,-33.58575 -54.24463,33.68438"
id="path3119"
style="fill:#f58113;fill-opacity:1;fill-rule:nonzero;stroke:none"
inkscape:connector-curvature="0" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.3 KiB