Skip to content

Commit 32c35e5

Browse files
committed
Merge branch 'feature/urdf' into develop
2 parents 758d43d + 83a2d0d commit 32c35e5

File tree

8 files changed

+303
-156
lines changed

8 files changed

+303
-156
lines changed
Lines changed: 3 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Loading

README.md

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- [Using RViz for Turtlebot](#using-rviz-for-turtlebot)
1111
- [Running Universal Robot UR10 Arm Test Scene with MoveIt!](#running-universal-robot-ur10-arm-test-scene-with-moveit)
1212
- [Running Image Segmentation Test](#running-image-segmentation-test)
13+
- [Export URDF](#export-urdf)
1314

1415
ZeroSim is a robotics simulation engine built on the easy to use [Unity 3D](https://unity.com/) development platform and the power of the [Robotics Operating System (ROS)](https://www.ros.org/). ZeroSim is designed for ease of use and rapid development of all sorts of robotics and simulation -- from warehouses and industrial settings, to farming and outdoors -- from robotic arms to ground and drone based mobile robots.
1516

@@ -55,9 +56,11 @@ ZeroSim provides a multitude of tools for building robots and environments in Un
5556
* Machine Learning tools:
5657
* Image Segmentation for training semantic segmentation algorithms.
5758

59+
* URDF Export
60+
5861
* **COMING SOON:**
5962
* More complete documentation.
60-
* URDF import & export.
63+
* URDF import.
6164
* Finish Docker integration with Unity. (Currently incomplete)
6265
* Secure communications via WebSockets.
6366
* Support for other Physics engines such as Bullet or Havok.
@@ -186,3 +189,15 @@ roslaunch zero_sim_ros basic_unity_editor.launch
186189
7. In the VNC window press the *LEFT* mouse button and select "Terminal". ![noVNC Terminal](Documentation~/images/novnc_terminal.png)
187190
8. In the new terminal run `rqt_image_view /image/segmentation_image`.
188191
9. Open up a second terminal and run `rqt_image_view /image/image_raw` ![RQT Image View Segmentation](Documentation~/images/rqt_image_view_segmentation.png)
192+
193+
194+
### Export URDF
195+
196+
1. Make sure that the ZeroSim samples are installed as outlined above.
197+
2. Open scene the `URDF_test.scene` in the ZeroSim samples.
198+
3. Select `SimpleRobotArm` in the scene hierarchy.
199+
![Select Simple Robot Arm](Documentation~/images/select_simple_robot_arm.png)
200+
4. Select `Export URDF` in the root properties view. ![Export URDF](Documentation~/images/export_urdf.png)
201+
5. Select the directory to export to.
202+
6. An excellent online URDF viewer is available: https://gkjohnson.github.io/urdf-loaders/javascript/example/index.html Just drag and drop the files exported above.
203+

Runtime/Scripts/Util/ImportExport/ZOExportOBJ.cs

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,49 @@ protected void End() {
4343
_startIndex = 0;
4444
}
4545

46+
protected string MeshToString(Mesh mesh, ZOExportOBJ.Orientation orientation) {
4647

47-
protected string MeshToString(MeshFilter meshFilter, Transform transform, bool applyLocalTransform, ZOExportOBJ.Orientation orientation) {
48+
int numVertices = 0;
49+
50+
StringBuilder sb = new StringBuilder();
51+
52+
foreach (Vector3 vv in mesh.vertices) {
53+
Vector3 v = vv;
54+
numVertices++;
55+
if (orientation == Orientation.Unity) {
56+
sb.AppendLine($"v {v.x} {v.y} {v.z}");
57+
} else if (orientation == Orientation.URDF) {
58+
sb.AppendLine($"v {v.z} {v.x} {v.y}");
59+
}
60+
}
61+
sb.AppendLine();
62+
foreach (Vector3 nn in mesh.normals) {
63+
Vector3 v = nn;
64+
if (orientation == Orientation.Unity) {
65+
sb.AppendLine($"vn {v.x} {v.y} {v.z}");
66+
} else if (orientation == Orientation.URDF) {
67+
sb.AppendLine($"vn {v.z} {v.x} {v.y}");
68+
}
69+
}
70+
71+
for (int submesh = 0; submesh < mesh.subMeshCount; submesh++) {
72+
int[] triangles = mesh.GetTriangles(submesh);
73+
for (int i = 0; i < triangles.Length; i += 3) {
74+
sb.AppendLine(string.Format("f {0}//{0} {1}//{1} {2}//{2}", // only exporting position and normal, no UV
75+
triangles[i] + 1 + _startIndex,
76+
triangles[i + 1] + 1 + _startIndex,
77+
triangles[i + 2] + 1 + _startIndex));
78+
79+
}
80+
81+
}
82+
83+
_startIndex += numVertices;
84+
return sb.ToString();
85+
}
86+
87+
88+
protected string MeshFilterToString(MeshFilter meshFilter, Transform transform, bool applyLocalTransform, ZOExportOBJ.Orientation orientation) {
4889
Vector3 s = transform.localScale;
4990
Vector3 p = transform.localPosition;
5091
Quaternion r = transform.localRotation;
@@ -141,7 +182,7 @@ protected string ProcessTransform(Transform transform, bool makeSubmeshes, bool
141182

142183
MeshFilter meshFilter = transform.GetComponent<MeshFilter>();
143184
if (meshFilter != null) {
144-
meshString.Append(MeshToString(meshFilter, transform, applyLocalTransform, orientation));
185+
meshString.Append(MeshFilterToString(meshFilter, transform, applyLocalTransform, orientation));
145186
}
146187

147188
for (int i = 0; i < transform.childCount; i++) {
@@ -265,6 +306,26 @@ public void ExportToDirectory(GameObject gameObject, string directoryPath, bool
265306
}
266307

267308
}
309+
310+
public void ExportMesh(Mesh mesh, string meshPath, ZOExportOBJ.Orientation orientation) {
311+
Start();
312+
313+
StringBuilder meshString = new StringBuilder();
314+
315+
meshString.Append("#" + Path.GetFileName(meshPath)
316+
+ "\n#" + System.DateTime.Now.ToLongDateString()
317+
+ "\n#" + System.DateTime.Now.ToLongTimeString()
318+
+ "\n#-------"
319+
+ "\n\n");
320+
321+
meshString.Append(MeshToString(mesh, orientation));
322+
323+
OBJString = meshString.ToString();
324+
325+
using (StreamWriter sw = new StreamWriter(meshPath)) {
326+
sw.Write(OBJString);
327+
}
328+
}
268329
}
269330

270331
}

Runtime/Scripts/Util/ImportExport/ZOExportURDF.cs

Lines changed: 99 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,11 +54,11 @@ public Vector3 ConnectedAnchor {
5454

5555
private List<Transform> _visualMeshesToExport = new List<Transform>();
5656
public List<Transform> VisualMeshesToExport {
57-
get {return _visualMeshesToExport; }
58-
}
57+
get { return _visualMeshesToExport; }
58+
}
5959

60-
private List<Transform> _collisionMeshesToExport = new List<Transform>();
61-
public List<Transform> CollisionMeshesToExport {
60+
private List<Mesh> _collisionMeshesToExport = new List<Mesh>();
61+
public List<Mesh> CollisionMeshesToExport {
6262
get { return _collisionMeshesToExport; }
6363
}
6464

@@ -70,14 +70,15 @@ public static void ExportToDirectory(ZOSimDocumentRoot documentRoot, string dire
7070
urdfXML.Save(urdfFilePath);
7171

7272
// save out visual and collision meshes
73-
foreach(Transform meshTransform in exportURDF.VisualMeshesToExport) {
73+
foreach (Transform meshTransform in exportURDF.VisualMeshesToExport) {
7474
ZOExportOBJ exportOBJ = new ZOExportOBJ();
7575
exportOBJ.ExportToDirectory(meshTransform.gameObject, directoryPath, true, false, ZOExportOBJ.Orientation.URDF);
7676
}
7777

78-
foreach(Transform meshTransform in exportURDF.CollisionMeshesToExport) {
78+
foreach (Mesh mesh in exportURDF.CollisionMeshesToExport) {
7979
ZOExportOBJ exportOBJ = new ZOExportOBJ();
80-
exportOBJ.ExportToDirectory(meshTransform.gameObject, directoryPath, true, false, ZOExportOBJ.Orientation.URDF);
80+
string collisionMeshFilePath = Path.Combine(directoryPath, $"{mesh.name}_collider.obj");
81+
exportOBJ.ExportMesh(mesh, collisionMeshFilePath, ZOExportOBJ.Orientation.URDF);
8182
}
8283

8384
Debug.Log($"INFO: ZOExportURDF Saved URDF: {urdfFilePath}");
@@ -272,13 +273,104 @@ protected void BuildURDFLink(ZOSimOccurrence simOccurrence, XElement robot, Vect
272273
foreach (Transform visualsChild in child) {
273274
// check if it is a primitive type (cube, sphere, cylinder, etc)
274275
BuildURDFVisuals(visualsChild, link, anchorOffset);
276+
277+
// we will do any collider that are attached to the visual
278+
BuildURDFCollisions(visualsChild, link, anchorOffset);
279+
}
280+
}
281+
282+
if (child.name.ToLower() == "collisions") {
283+
// go through the children of the collisions and get all the models
284+
foreach (Transform collisionChild in child) {
285+
BuildURDFCollisions(collisionChild, link, anchorOffset);
275286
}
276287
}
277288

278289
}
279290
}
280291

281292

293+
protected void BuildURDFCollisions(Transform collisionTransform, XElement link, Vector3 anchorOffset) {
294+
Collider[] colliders = collisionTransform.GetComponentsInChildren<Collider>();
295+
foreach (Collider collider in colliders) {
296+
XElement collision = new XElement("collision");
297+
collision.SetAttributeValue("name", collisionTransform.name);
298+
XElement geometry = new XElement("geometry");
299+
Vector3 center = Vector3.zero;
300+
301+
if (collider.GetType() == typeof(BoxCollider)) {
302+
BoxCollider boxCollider = collider as BoxCollider;
303+
XElement box = new XElement("box");
304+
305+
Vector3 boxSize = new Vector3(boxCollider.size.x * collider.transform.localScale.x,
306+
boxCollider.size.y * collider.transform.localScale.y,
307+
boxCollider.size.z * collider.transform.localScale.z);
308+
box.SetAttributeValue("size", boxSize.Unity2RosScale().ToXMLString());
309+
geometry.Add(box);
310+
311+
center = new Vector3(boxCollider.center.x * boxCollider.transform.localScale.x,
312+
boxCollider.center.y * boxCollider.transform.localScale.y,
313+
boxCollider.center.z * boxCollider.transform.localScale.z);
314+
} else if (collider.GetType() == typeof(SphereCollider)) {
315+
SphereCollider sphereCollider = collider as SphereCollider;
316+
XElement sphere = new XElement("sphere");
317+
float radius = sphereCollider.radius * collider.transform.localScale.x;
318+
sphere.SetAttributeValue("radius", radius);
319+
geometry.Add(sphere);
320+
321+
center = new Vector3(sphereCollider.center.x * sphereCollider.transform.localScale.x,
322+
sphereCollider.center.y * sphereCollider.transform.localScale.y,
323+
sphereCollider.center.z * sphereCollider.transform.localScale.z);
324+
325+
326+
} else if (collider.GetType() == typeof(CapsuleCollider)) {
327+
CapsuleCollider capsuleCollider = collider as CapsuleCollider;
328+
XElement cylinder = new XElement("cylinder");
329+
float radius = capsuleCollider.radius * collider.transform.localScale.x;
330+
float height = capsuleCollider.height * collider.transform.localScale.y;
331+
cylinder.SetAttributeValue("radius", radius);
332+
cylinder.SetAttributeValue("length", height);
333+
geometry.Add(cylinder);
334+
335+
center = new Vector3(capsuleCollider.center.x * capsuleCollider.transform.localScale.x,
336+
capsuleCollider.center.y * capsuleCollider.transform.localScale.y,
337+
capsuleCollider.center.z * capsuleCollider.transform.localScale.z);
338+
339+
340+
} else if (collider.GetType() == typeof(MeshCollider)) {
341+
MeshCollider meshCollider = collider as MeshCollider;
342+
_collisionMeshesToExport.Add(meshCollider.sharedMesh);
343+
344+
XElement mesh = new XElement("mesh");
345+
mesh.SetAttributeValue("filename", $"{meshCollider.sharedMesh.name}_collider.obj");
346+
Vector3 scale = collisionTransform.localScale;
347+
mesh.SetAttributeValue("scale", scale.ToXMLString());
348+
geometry.Add(mesh);
349+
350+
}
351+
352+
353+
if (geometry.HasElements) {
354+
// build origin
355+
Vector3 position = collisionTransform.localPosition + anchorOffset + center;
356+
Vector3 xyz = position.Unity2Ros();
357+
Vector3 rpy = new Vector3(-collisionTransform.localEulerAngles.z * Mathf.Deg2Rad,
358+
collisionTransform.localEulerAngles.x * Mathf.Deg2Rad,
359+
-collisionTransform.localEulerAngles.y * Mathf.Deg2Rad);
360+
361+
XElement origin = new XElement("origin");
362+
origin.SetAttributeValue("xyz", xyz.ToXMLString());
363+
origin.SetAttributeValue("rpy", rpy.ToXMLString());
364+
365+
collision.Add(origin);
366+
367+
collision.Add(geometry);
368+
link.Add(collision);
369+
}
370+
371+
}
372+
}
373+
282374
protected void BuildURDFVisuals(Transform visualTransform, XElement link, Vector3 anchorOffset) {
283375
// build 3d primitive if exists
284376
MeshFilter meshFilter = visualTransform.GetComponent<MeshFilter>();

0 commit comments

Comments
 (0)