diff --git a/visualization/CMakeLists.txt b/visualization/CMakeLists.txt
new file mode 100644
index 0000000..cf86d52
--- /dev/null
+++ b/visualization/CMakeLists.txt
@@ -0,0 +1,17 @@
+cmake_minimum_required(VERSION 2.4.6)
+include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
+
+# Append to CPACK_SOURCE_IGNORE_FILES a semicolon-separated list of
+# directories (or patterns, but directories should suffice) that should
+# be excluded from the distro. This is not the place to put things that
+# should be ignored everywhere, like "build" directories; that happens in
+# rosbuild/rosbuild.cmake. Here should be listed packages that aren't
+# ready for inclusion in a distro.
+#
+# This list is combined with the list in rosbuild/rosbuild.cmake. Note
+# that CMake 2.6 may be required to ensure that the two lists are combined
+# properly. CMake 2.4 seems to have unpredictable scoping rules for such
+# variables.
+#list(APPEND CPACK_SOURCE_IGNORE_FILES /core/experimental)
+
+rosbuild_make_distribution(0.1.2)
diff --git a/visualization/Makefile b/visualization/Makefile
new file mode 100644
index 0000000..a818cca
--- /dev/null
+++ b/visualization/Makefile
@@ -0,0 +1 @@
+include $(shell rospack find mk)/cmake_stack.mk
\ No newline at end of file
diff --git a/visualization/joint_state_publisher/CMakeLists.txt b/visualization/joint_state_publisher/CMakeLists.txt
new file mode 100644
index 0000000..172441e
--- /dev/null
+++ b/visualization/joint_state_publisher/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 2.4.6)
+include($ENV{ROS_ROOT}/core/rosbuild/rosbuild.cmake)
+
+rosbuild_init()
+
diff --git a/visualization/joint_state_publisher/Makefile b/visualization/joint_state_publisher/Makefile
new file mode 100644
index 0000000..b75b928
--- /dev/null
+++ b/visualization/joint_state_publisher/Makefile
@@ -0,0 +1 @@
+include $(shell rospack find mk)/cmake.mk
\ No newline at end of file
diff --git a/visualization/joint_state_publisher/joint_state_publisher b/visualization/joint_state_publisher/joint_state_publisher
new file mode 100755
index 0000000..ae5aeb2
--- /dev/null
+++ b/visualization/joint_state_publisher/joint_state_publisher
@@ -0,0 +1,206 @@
+#!/usr/bin/python
+
+import roslib; roslib.load_manifest('joint_state_publisher')
+import rospy
+import wx
+import xml.dom.minidom
+from sensor_msgs.msg import JointState
+from math import pi
+from threading import Thread
+
+RANGE = 10000
+
+def get_param(name, value=None):
+ private = "~%s" % name
+ if rospy.has_param(private):
+ return rospy.get_param(private)
+ elif rospy.has_param(name):
+ return rospy.get_param(name)
+ else:
+ return value
+
+class JointStatePublisher():
+ def __init__(self):
+ description = get_param('robot_description')
+ robot = xml.dom.minidom.parseString(description).getElementsByTagName('robot')[0]
+ self.free_joints = {}
+ self.joint_list = [] # for maintaining the original order of the joints
+ self.dependent_joints = get_param("dependent_joints", {})
+
+ # Find all non-fixed joints
+ for child in robot.childNodes:
+ if child.nodeType is child.TEXT_NODE:
+ continue
+ if child.localName == 'joint':
+ jtype = child.getAttribute('type')
+ if jtype == 'fixed':
+ continue
+ name = child.getAttribute('name')
+ if jtype == 'continuous':
+ minval = -pi
+ maxval = pi
+ else:
+ limit = child.getElementsByTagName('limit')[0]
+ minval = float(limit.getAttribute('lower'))
+ maxval = float(limit.getAttribute('upper'))
+
+ if name in self.dependent_joints:
+ continue
+ if minval > 0 or maxval < 0:
+ zeroval = (maxval + minval)/2
+ else:
+ zeroval = 0
+
+ joint = {'min':minval, 'max':maxval, 'zero':zeroval, 'value':zeroval }
+ self.free_joints[name] = joint
+ self.joint_list.append(name)
+
+ use_gui = get_param("use_gui", False)
+
+ if use_gui:
+ app = wx.App()
+ self.gui = JointStatePublisherGui("Joint State Publisher", self)
+ self.gui.Show()
+ Thread(target=app.MainLoop).start()
+ else:
+ self.gui = None
+
+ source_list = get_param("source_list", [])
+ self.sources = []
+ for source in source_list:
+ self.sources.append(rospy.Subscriber(source, JointState, self.source_cb))
+
+ self.pub = rospy.Publisher('joint_states', JointState)
+
+ def source_cb(self, msg):
+ for i in range(len(msg.name)):
+ name = msg.name[i]
+ position = msg.position[i]
+ if name in self.free_joints:
+ joint = self.free_joints[name]
+ joint['value'] = position
+ if self.gui is not None:
+ self.gui.update_sliders()
+
+
+ def loop(self):
+ hz = get_param("rate", 10) # 10hz
+ r = rospy.Rate(hz)
+
+ # Publish Joint States
+ while not rospy.is_shutdown():
+ msg = JointState()
+ msg.header.stamp = rospy.Time.now()
+
+ # Add Free Joints
+ for (name,joint) in self.free_joints.items():
+ msg.name.append(str(name))
+ msg.position.append(joint['value'])
+
+ # Add Dependent Joints
+ for (name,param) in self.dependent_joints.items():
+ parent = param['parent']
+ baseval = self.free_joints[parent]['value']
+ value = baseval * param.get('factor', 1)
+
+ msg.name.append(str(name))
+ msg.position.append(value)
+
+ self.pub.publish(msg)
+
+ r.sleep()
+
+class JointStatePublisherGui(wx.Frame):
+ def __init__(self, title, jsp):
+ wx.Frame.__init__(self, None, -1, title, (-1, -1));
+ self.jsp = jsp
+ self.joint_map = {}
+ panel = wx.Panel(self, wx.ID_ANY);
+ box = wx.BoxSizer(wx.VERTICAL)
+ font = wx.Font(9, wx.SWISS, wx.NORMAL, wx.BOLD)
+
+ ### Sliders ###
+ for name in self.jsp.joint_list:
+ joint = self.jsp.free_joints[name]
+
+ if joint['min'] == joint['max']:
+ continue
+
+ row = wx.GridSizer(1,2)
+ label = wx.StaticText(panel, -1, name)
+ label.SetFont(font)
+ row.Add(label, 1, wx.ALIGN_CENTER_VERTICAL)
+
+ display = wx.TextCtrl (panel, value=str(0),
+ style=wx.TE_READONLY | wx.ALIGN_RIGHT)
+
+ row.Add(display, flag= wx.ALIGN_RIGHT| wx.ALIGN_CENTER_VERTICAL)
+ box.Add(row, 1, wx.EXPAND)
+ slider = wx.Slider(panel, -1, RANGE/2, 0, RANGE,
+ style= wx.SL_AUTOTICKS | wx.SL_HORIZONTAL)
+ slider.SetFont(font)
+ box.Add(slider, 1, wx.EXPAND)
+
+ self.joint_map[name] = {'slidervalue':0, 'display':display,
+ 'slider':slider, 'joint':joint}
+
+ ### Buttons ###
+ self.ctrbutton = wx.Button(panel, 1, 'Center')
+ self.Bind(wx.EVT_SLIDER, self.sliderUpdate)
+
+ wx.EVT_BUTTON(self, 1, self.center_event)
+
+ box.Add(self.ctrbutton, 0, wx.EXPAND)
+
+ panel.SetSizer(box)
+ self.center()
+ box.Fit(self)
+ self.update_values()
+
+
+ def update_values(self):
+ for (name,joint_info) in self.joint_map.items():
+ purevalue = joint_info['slidervalue']
+ joint = joint_info['joint']
+ value = self.sliderToValue(purevalue, joint)
+ joint['value'] = value
+ self.update_sliders()
+
+ def update_sliders(self):
+ for (name,joint_info) in self.joint_map.items():
+ joint = joint_info['joint']
+ joint_info['slidervalue'] = self.valueToSlider(joint['value'], joint)
+ joint_info['slider'].SetValue(joint_info['slidervalue'])
+ joint_info['display'].SetValue("%.2f"%joint['value'])
+
+ def center_event(self, event):
+ self.center()
+
+ def center(self):
+ rospy.loginfo("Centering")
+ for (name,joint_info) in self.joint_map.items():
+ joint = joint_info['joint']
+ joint_info['slidervalue'] = self.valueToSlider(joint['zero'], joint)
+ self.update_values()
+
+ def sliderUpdate(self, event):
+ for (name,joint_info) in self.joint_map.items():
+ joint_info['slidervalue'] = joint_info['slider'].GetValue()
+ self.update_values()
+
+ def valueToSlider(self, value, joint):
+ return (value - joint['min']) * float(RANGE) / (joint['max'] - joint['min'])
+
+ def sliderToValue(self, slider, joint):
+ pctvalue = slider / float(RANGE)
+ return joint['min'] + (joint['max']-joint['min']) * pctvalue
+
+
+if __name__ == '__main__':
+ try:
+ rospy.init_node('joint_state_publisher')
+ jsp = JointStatePublisher()
+ jsp.loop()
+
+ except rospy.ROSInterruptException: pass
+
diff --git a/visualization/joint_state_publisher/manifest.xml b/visualization/joint_state_publisher/manifest.xml
new file mode 100644
index 0000000..5447c5d
--- /dev/null
+++ b/visualization/joint_state_publisher/manifest.xml
@@ -0,0 +1,15 @@
+
+
+ A node for publishing joint angles, either through a gui, or with
+ default values.
+
+ David Lu!!
+ BSD
+
+ http://ros.org/wiki/joint_state_publisher
+
+
+
+
+
+
diff --git a/visualization/joint_state_publisher/screenshot.png b/visualization/joint_state_publisher/screenshot.png
new file mode 100644
index 0000000..cb8a607
Binary files /dev/null and b/visualization/joint_state_publisher/screenshot.png differ
diff --git a/visualization/stack.xml b/visualization/stack.xml
new file mode 100644
index 0000000..c802c17
--- /dev/null
+++ b/visualization/stack.xml
@@ -0,0 +1,11 @@
+
+ This package contains a number of tools for making, viewing and using URDF models.
+ Maintained by David Lu!!
+ BSD
+
+ http://ros.org/wiki/robot_model_visualization
+
+
+
+
+