def __init__(self, plant, scene_graph, diagram): self.plant = plant self.scene_graph = scene_graph self.diagram = diagram # Assumes single DOF per Joint. joints = [] for i in range(plant.num_joints()): joint = plant.get_joint(JointIndex(i)) assert joint.name(), joint.name() if joint.num_positions() == 0: continue assert joint.num_positions() == 1, (joint, joint.name()) assert isinstance(joint, self._acceptable_joints) joints.append(joint) self._joint_map = {} self._joint_names = [] joints = sorted(joints, key=lambda x: x.position_start()) for joint in joints: self._joint_names.append(joint.name()) self._joint_map[joint.name()] = joint # Get frames. nf = plant.num_frames() self._frames = [] for i in range(nf): frame = plant.get_frame(FrameIndex(i)) if frame.name() and not frame.name().startswith("_"): self._frames.append(frame) assert len(self._frames) > 0
def MakeNamedViewPositions(mbp, view_name): names = [None] * mbp.num_positions() for ind in range(mbp.num_joints()): joint = mbp.get_joint(JointIndex(ind)) for i in range(joint.num_positions()): names[joint.position_start() + i] = \ f"{joint.name()}_{joint.position_suffix(i)}" for ind in mbp.GetFloatingBaseBodies(): body = mbp.get_body(ind) start = body.floating_positions_start() for i in range(7): names[start + i] = body.name() + body.floating_position_suffix(i) return namedview(view_name, names)
def MakeNamedViewVelocities(mbp, view_name): names = [None] * mbp.num_velocities() for ind in range(mbp.num_joints()): joint = mbp.get_joint(JointIndex(ind)) for i in range(joint.num_velocities()): names[joint.velocity_start() + i] = \ f"{joint.name()}_{joint.velocity_suffix(i)}" for ind in mbp.GetFloatingBaseBodies(): body = mbp.get_body(ind) start = body.floating_velocities_start() for i in range(6): names[start + i] = f"{body.name()}_{body.floating_velocity_suffix(i)}" return namedview(view_name, names)
def test_multibody_plant_api_via_parsing(self): # TODO(eric.cousineau): Decouple this when construction can be done # without parsing. # This a subset of `multibody_plant_sdf_parser_test.cc`. file_name = FindResourceOrThrow( "drake/multibody/benchmarks/acrobot/acrobot.sdf") plant = MultibodyPlant(time_step=0.01) model_instance = Parser(plant).AddModelFromFile(file_name) self.assertIsInstance(model_instance, ModelInstanceIndex) plant.Finalize() benchmark = MakeAcrobotPlant(AcrobotParameters(), True) self.assertEqual(plant.num_bodies(), benchmark.num_bodies()) self.assertEqual(plant.num_joints(), benchmark.num_joints()) self.assertEqual(plant.num_actuators(), benchmark.num_actuators()) self.assertEqual(plant.num_model_instances(), benchmark.num_model_instances() + 1) self.assertEqual(plant.num_positions(), benchmark.num_positions()) self.assertEqual(plant.num_positions(model_instance=model_instance), benchmark.num_positions()) self.assertEqual(plant.num_velocities(), benchmark.num_velocities()) self.assertEqual(plant.num_multibody_states(), benchmark.num_multibody_states()) self.assertEqual(plant.num_actuated_dofs(), benchmark.num_actuated_dofs()) self.assertTrue(plant.is_finalized()) self.assertTrue(plant.HasBodyNamed(name="Link1")) self.assertTrue( plant.HasBodyNamed(name="Link1", model_instance=model_instance)) self.assertTrue(plant.HasJointNamed(name="ShoulderJoint")) self.assertTrue( plant.HasJointNamed(name="ShoulderJoint", model_instance=model_instance)) shoulder = plant.GetJointByName(name="ShoulderJoint") self._test_joint_api(shoulder) np.testing.assert_array_equal(shoulder.position_lower_limits(), [-np.inf]) np.testing.assert_array_equal(shoulder.position_upper_limits(), [np.inf]) self.assertIs( shoulder, plant.GetJointByName(name="ShoulderJoint", model_instance=model_instance)) self._test_joint_actuator_api( plant.GetJointActuatorByName(name="ElbowJoint")) self._test_body_api(plant.GetBodyByName(name="Link1")) self.assertIs( plant.GetBodyByName(name="Link1"), plant.GetBodyByName(name="Link1", model_instance=model_instance)) self.assertEqual(len(plant.GetBodyIndices(model_instance)), 2) self._test_frame_api(plant.GetFrameByName(name="Link1")) self.assertIs( plant.GetFrameByName(name="Link1"), plant.GetFrameByName(name="Link1", model_instance=model_instance)) self.assertEqual(model_instance, plant.GetModelInstanceByName(name="acrobot")) self.assertIsInstance(plant.get_actuation_input_port(), InputPort) self.assertIsInstance(plant.get_state_output_port(), OutputPort) # Smoke test of deprecated methods. with catch_drake_warnings(expected_count=2): plant.get_continuous_state_output_port() plant.get_continuous_state_output_port(model_instance) self.assertIsInstance(plant.get_contact_results_output_port(), OutputPort) self.assertIsInstance(plant.num_frames(), int) self.assertIsInstance(plant.get_body(body_index=BodyIndex(0)), Body) self.assertIs(shoulder, plant.get_joint(joint_index=JointIndex(0))) self.assertIsInstance( plant.get_joint_actuator(actuator_index=JointActuatorIndex(0)), JointActuator) self.assertIsInstance(plant.get_frame(frame_index=FrameIndex(0)), Frame) self.assertEqual( "acrobot", plant.GetModelInstanceName(model_instance=model_instance))
def __init__(self, meshcat, plant, root_context=None, lower_limit=-10., upper_limit=10., resolution=0.01): """ Creates an meshcat slider for each joint in the plant. Args: meshcat: A Meshcat instance. plant: A MultibodyPlant. publishing_system: The System whose Publish method will be called. Can be the entire Diagram, but can also be a subsystem. root_context: A mutable root Context of the Diagram containing the ``plant``; we will extract the subcontext's using `GetMyContextFromRoot`. lower_limit: A scalar or vector of length robot.num_positions(). The lower limit of the slider will be the maximum value of this number and any limit specified in the Joint. upper_limit: A scalar or vector of length robot.num_positions(). The upper limit of the slider will be the minimum value of this number and any limit specified in the Joint. resolution: A scalar or vector of length robot.num_positions() that specifies the step argument of the FloatSlider. """ LeafSystem.__init__(self) def _broadcast(x, num): x = np.array(x) assert len(x.shape) <= 1 return np.array(x) * np.ones(num) lower_limit = _broadcast(lower_limit, plant.num_positions()) upper_limit = _broadcast(upper_limit, plant.num_positions()) resolution = _broadcast(resolution, plant.num_positions()) self._meshcat = meshcat self._plant = plant plant_context = plant.GetMyContextFromRoot(root_context) if \ root_context else plant.CreateDefaultContext() self._sliders = {} self._positions = plant.GetPositions(plant_context) slider_num = 0 for i in range(plant.num_joints()): joint = plant.get_joint(JointIndex(i)) low = joint.position_lower_limits() upp = joint.position_upper_limits() for j in range(joint.num_positions()): index = joint.position_start() + j description = joint.name() if joint.num_positions() > 1: description += '_' + joint.position_suffix(j) meshcat.AddSlider(value=self._positions[index], min=max(low[j], lower_limit[slider_num]), max=min(upp[j], upper_limit[slider_num]), step=resolution[slider_num], name=description) self._sliders[index] = description slider_num += 1 port = self.DeclareVectorOutputPort("positions", plant.num_positions(), self.DoCalcOutput) port.disable_caching_by_default()
def __init__(self, robot, lower_limit=-10., upper_limit=10., resolution=0.01, length=200, update_period_sec=0.005): """" Args: robot: A MultibodyPlant. lower_limit: A scalar or vector of length robot.num_positions(). The lower limit of the slider will be the maximum value of this number and any limit specified in the Joint. upper_limit: A scalar or vector of length robot.num_positions(). The upper limit of the slider will be the minimum value of this number and any limit specified in the Joint. resolution: A scalar or vector of length robot.num_positions() that specifies the discretization of the slider. length: The length of the sliders. update_period_sec: Specifies how often the window update() method gets called. """ VectorSystem.__init__(self, 0, robot.num_positions()) # The widgets themselves have undeclared state. For now, we accept it, # and simply disable caching on the output port. self.get_output_port(0).disable_caching_by_default() def _reshape(x, num): x = np.array(x) assert len(x.shape) <= 1 return np.array(x) * np.ones(num) lower_limit = _reshape(lower_limit, robot.num_positions()) upper_limit = _reshape(upper_limit, robot.num_positions()) resolution = _reshape(resolution, robot.num_positions()) # Schedule window updates in either case (new or existing window): self.DeclarePeriodicPublish(update_period_sec, 0.0) self._slider = [] self._slider_position_start = [] context = robot.CreateDefaultContext() self._default_position = robot.GetPositions(context) k = 0 for i in range(0, robot.num_joints()): joint = robot.get_joint(JointIndex(i)) low = joint.position_lower_limits() upp = joint.position_upper_limits() for j in range(0, joint.num_positions()): index = joint.position_start() + j description = joint.name() if joint.num_positions() > 1: description += f"[{j}]" self._slider_position_start.append(index) self._slider.append( FloatSlider(value=self._default_position[index], min=max(low[j], lower_limit[k]), max=min(upp[j], upper_limit[k]), step=resolution[k], continuous_update=True, description=description, style={'description_width': 'initial'}, layout=Layout(width=f"'{length}'"))) display(self._slider[k]) k += 1
def MakeJointSlidersThatPublishOnCallback(plant, publishing_system, root_context, my_callback=None, lower_limit=-10., upper_limit=10., resolution=0.01, length=200, continuous_update=True): """ Creates an ipywidget slider for each joint in the plant. Unlike the JointSliders System, we do not expect this to be used in a Simulator. It simply updates the context and calls Publish directly from the slider callback. Args: plant: A MultibodyPlant. publishing_system: The System whose Publish method will be called. Can be the entire Diagram, but can also be a subsystem. root_context: A mutable root Context of the Diagram containing both the ``plant`` and the ``publishing_system``; we will extract the subcontext's using `GetMyContextFromRoot`. my_callback: An optional additional callback function that will be called once immediately and again whenever the sliders are moved, using ``my_callback(plant_context)``. This can be useful, e.g. for outputting text that prints the current end-effector Jacobian, or for showing a rendered image from a camera. lower_limit: A scalar or vector of length robot.num_positions(). The lower limit of the slider will be the maximum value of this number and any limit specified in the Joint. upper_limit: A scalar or vector of length robot.num_positions(). The upper limit of the slider will be the minimum value of this number and any limit specified in the Joint. resolution: A scalar or vector of length robot.num_positions() that specifies the step argument of the FloatSlider. length: The length of the sliders, which will be passed as a string to the CSS width field and can be any valid CSS entry (e.g. 100, 100px). continuous_update: The continuous_update field for the FloatSliders. The default ``True`` means that this method will publish/ callback as the sliders are dragged. ``False`` means that the publish/callback will only happen once the user finishes dragging the slider. Returns: A list of the slider widget objects that are created. Note: Some publishers (like MeshcatVisualizer) use an initialization event to "load" the geometry. You should call that *before* calling this method (e.g. with `meshcat.load()`). """ def _broadcast(x, num): x = np.array(x) assert len(x.shape) <= 1 return np.array(x) * np.ones(num) lower_limit = _broadcast(lower_limit, plant.num_positions()) upper_limit = _broadcast(upper_limit, plant.num_positions()) resolution = _broadcast(resolution, plant.num_positions()) publishing_context = publishing_system.GetMyContextFromRoot(root_context) plant_context = plant.GetMyContextFromRoot(root_context) positions = plant.GetPositions(plant_context) # Publish once immediately. publishing_system.Publish(publishing_context) if my_callback: my_callback(plant_context) def _slider_callback(change, index): positions[index] = change.new plant.SetPositions(plant_context, positions) publishing_system.Publish(publishing_context) if my_callback: my_callback(plant_context) slider_widgets = [] slider_num = 0 for i in range(plant.num_joints()): joint = plant.get_joint(JointIndex(i)) low = joint.position_lower_limits() upp = joint.position_upper_limits() for j in range(joint.num_positions()): index = joint.position_start() + j description = joint.name() if joint.num_positions() > 1: description += f"[{j}]" slider = FloatSlider(value=positions[index], min=max(low[j], lower_limit[slider_num]), max=min(upp[j], upper_limit[slider_num]), step=resolution[slider_num], continuous_update=continuous_update, description=description, style={'description_width': 'initial'}, layout=Layout(width=f"'{length}'")) slider.observe(partial(_slider_callback, index=index), names='value') display(slider) slider_widgets.append(slider) slider_num += 1 return slider_widgets
def __init__(self, robot, lower_limit=-10., upper_limit=10., resolution=-1, length=200, update_period_sec=0.005, window=None, title=None): """" Args: robot: A MultibodyPlant. lower_limit: A scalar or vector of length robot.num_positions(). The lower limit of the slider will be the maximum value of this number and any limit specified in the Joint. upper_limit: A scalar or vector of length robot.num_positions(). The upper limit of the slider will be the minimum value of this number and any limit specified in the Joint. resolution: A scalar or vector of length robot.num_positions() that specifies the discretization of the slider. Use -1 (the default) to disable any rounding. length: The length of the sliders (passed as an argument to tk.Scale). update_period_sec: Specifies how often the window update() method gets called. window: Optionally pass in a tkinter.Tk() object to add these widgets to. Default behavior is to create a new window. title: The string that appears as the title of the gui window. Use None to generate a default title. This parameter is only used if a window==None. """ VectorSystem.__init__(self, 0, robot.num_positions()) def _reshape(x, num): x = np.array(x) assert len(x.shape) <= 1 return np.array(x) * np.ones(num) lower_limit = _reshape(lower_limit, robot.num_positions()) upper_limit = _reshape(upper_limit, robot.num_positions()) resolution = _reshape(resolution, robot.num_positions()) if title is None: if robot.num_model_instances() == 1: title = robot.GetModelInstanceName(0) + " Joints" else: title = "Multibody Joints" if window is None: self.window = tk.Tk() self.window.title(title) else: self.window = window # Schedule window updates in either case (new or existing window): self.DeclarePeriodicEvent(update_period_sec, 0.0, PublishEvent(self._update)) self._slider = [] self._slider_position_start = [] context = robot.CreateDefaultContext() state = robot.GetPositionsAndVelocities(context) self._default_position = state[:robot.num_positions()] k = 0 for i in range(0, robot.num_joints()): joint = robot.get_joint(JointIndex(i)) low = joint.position_lower_limits() upp = joint.position_upper_limits() for j in range(0, joint.num_positions()): self._slider_position_start.append(joint.position_start() + j) self._slider.append(tk.Scale(self.window, from_=max(low[j], lower_limit[k]), to=min(upp[j], upper_limit[k]), resolution=resolution[k], label=joint.name(), length=length, orient=tk.HORIZONTAL)) self._slider[k].pack() k += 1
def __init__(self, robot, lower_limit=-10., upper_limit=10., resolution=-1, length=200, update_period_sec=0.005, window=None, title=None): """" Args: robot: A MultibodyPlant. lower_limit: A scalar or vector of length robot.num_positions(). The lower limit of the slider will be the maximum value of this number and any limit specified in the Joint. upper_limit: A scalar or vector of length robot.num_positions(). The upper limit of the slider will be the minimum value of this number and any limit specified in the Joint. resolution: A scalar or vector of length robot.num_positions() that specifies the discretization of the slider. Use -1 (the default) to disable any rounding. update_period_sec: Specifies how often the window update() method gets called. window: Optionally pass in a tkinter.Tk() object to add these widgets to. Default behavior is to create a new window. title: The string that appears as the title of the gui window. Use None to generate a default title. This parameter is only used if a window==None. """ VectorSystem.__init__(self, 0, robot.num_positions()) def _reshape(x, num): x = np.array(x) assert len(x.shape) <= 1 return np.array(x) * np.ones(num) def _frame_config(event): """ Execute once to configure the canvas size, canvas contains scrollbar and slider_frame. """ self.canvas.configure(scrollregion=self.canvas.bbox("all"), width=200, height=min(robot.num_joints() * 60, 400)) lower_limit = _reshape(lower_limit, robot.num_positions()) upper_limit = _reshape(upper_limit, robot.num_positions()) resolution = _reshape(resolution, robot.num_positions()) if title is None: if robot.num_model_instances() == 1: title = robot.GetModelInstanceName(0) + " Joints" else: title = "Multibody Joints" if window is None: self.window = tk.Tk() self.window.title(title) else: self.window = window # Schedule window updates in either case (new or existing window): self.DeclarePeriodicPublish(update_period_sec, 0.0) self.window.geometry("220x410") # Allow only y dimension resizable. self.window.resizable(0, 1) # Once window is created, create window_frame # and canvas that host slider_frame and scrollbar. self.window_frame = tk.Frame(self.window, relief=tk.GROOVE, bd=1) self.window_frame.place(x=0, y=0) self.canvas = tk.Canvas(self.window_frame) # Init slider_frame that actually contain the sliders # and scrollbar. self.slider_frame = tk.Frame(self.canvas) self.scrollbar = tk.Scrollbar(self.window_frame, orient="vertical", command=self.canvas.yview) self.canvas.configure(yscrollcommand=self.scrollbar.set) # Put scrollbar and slider_frame into canvas. self.scrollbar.pack(side="right", fill="y") self.canvas.pack(side="left") self.canvas.create_window((0, 0), window=self.slider_frame, anchor='nw') # Call _frame_config to configure canvas size. self.slider_frame.bind("<Configure>", _frame_config) self._slider = [] self._slider_position_start = [] context = robot.CreateDefaultContext() state = robot.GetPositionsAndVelocities(context) self._default_position = state[:robot.num_positions()] k = 0 for i in range(0, robot.num_joints()): joint = robot.get_joint(JointIndex(i)) low = joint.position_lower_limits() upp = joint.position_upper_limits() for j in range(0, joint.num_positions()): self._slider_position_start.append(joint.position_start() + j) self._slider.append( tk.Scale(self.slider_frame, from_=max(low[j], lower_limit[k]), to=min(upp[j], upper_limit[k]), resolution=resolution[k], label=joint.name(), length=length, orient=tk.HORIZONTAL)) self._slider[k].pack() k += 1
model = parser.AddModelFromFile(sdf_file_path, model_name) # Weld to world so it doesn't fall through floor :D base_frame = plant.GetFrameByName("base", model) X_WB = RigidTransform([0, 0, 0]) plant.WeldFrames(plant.world_frame(), base_frame, X_WB) plant.Finalize() # Must happen after Finalize # RuntimeError: Pre-finalize calls to 'num_actuated_dofs()' are not allowed; you must call Finalize() first. no_control(plant, builder, model) joints = [] for i in range(plant.num_joints()): joints.append(plant.get_joint(JointIndex(i))) rclpy.init() node = rclpy.create_node('drake_demo') # Publish SDF content on robot_description topic latching_qos = QoSProfile(depth=1, durability=DurabilityPolicy.TRANSIENT_LOCAL) description_publisher = node.create_publisher(StringMsg, 'robot_description', qos_profile=latching_qos) msg = StringMsg() with open(sdf_file_path, 'r') as sdf_file: msg.data = sdf_file.read() description_publisher.publish(msg)
def MakeJointSlidersThatPublishOnCallback(plant, publishing_system, root_context, my_callback=None, lower_limit=-10., upper_limit=10., resolution=0.01, length=200, continuous_update=True, floating_base=True): """ Creates an ipywidget slider for each joint in the plant. Unlike the JointSliders System, we do not expect this to be used in a Simulator. It simply updates the context and calls Publish directly from the slider callback. Args: plant: A MultibodyPlant. publishing_system: The System whos Publish method will be called. Can be the entire Diagram, but can also be a subsystem. root_context: A mutable root Context of the Diagram containing both the ``plant`` and the ``publishing_system``; we will extract the subcontext's using `GetMyContextFromRoot`. my_callback: An optional additional callback function that will be called once immediately and again whenever the sliders are moved, using ``my_callback(plant_context)``. This can be useful, e.g. for outputting text that prints the current end-effector Jacobian, or for showing a rendered image from a camera. lower_limit: A scalar or vector of length robot.num_velocities(). The lower limit of the slider will be the maximum value of this number and any limit specified in the Joint. upper_limit: A scalar or vector of length robot.num_velocities(). The upper limit of the slider will be the minimum value of this number and any limit specified in the Joint. resolution: A scalar or vector of length robot.num_velocities() that specifies the step argument of the FloatSlider. length: The length of the sliders, which will be passed as a string to the CSS width field and can be any valid CSS entry (e.g. 100, 100px). continuous_update: The continuous_update field for the FloatSliders. The default ``True`` means that this method will publish/ callback as the sliders are dragged. ``False`` means that the publish/callback will only happen once the user finishes dragging the slider. floating_base: If True, then include XYZ/RPY sliders corresponding to the pose of any floating joints. Returns: A list of the slider widget objects that are created. Note: Some publishers (like MeshcatVisualizer) use an initialization event to "load" the geometry. You should call that *before* calling this method (e.g. with `meshcat.load()`). """ def _broadcast(x, num): x = np.asarray(x) assert len(x.shape) <= 1 return np.array(x) * np.ones(num) # Use num_velocities instead of num_positions because we want 6 per # floating base, not 7. lower_limit = _broadcast(lower_limit, plant.num_velocities()) upper_limit = _broadcast(upper_limit, plant.num_velocities()) resolution = _broadcast(resolution, plant.num_velocities()) publishing_context = publishing_system.GetMyContextFromRoot(root_context) plant_context = plant.GetMyContextFromRoot(root_context) positions = plant.GetPositions(plant_context) # Publish once immediately. publishing_system.Publish(publishing_context) if my_callback: my_callback(plant_context) def _slider_callback(change, index, body=None): if body: # Then it's a floating base pose = plant.GetFreeBodyPose(plant_context, body) vector_pose = np.concatenate( (RollPitchYaw(pose.rotation()).vector(), pose.translation())) vector_pose[index] = change.new plant.SetFreeBodyPose( plant_context, body, RigidTransform(RollPitchYaw(vector_pose[:3]), vector_pose[3:])) else: positions[index] = change.new plant.SetPositions(plant_context, positions) publishing_system.Publish(publishing_context) if my_callback: my_callback(plant_context) slider_widgets = [] for i in range(plant.num_joints()): joint = plant.get_joint(JointIndex(i)) low = joint.position_lower_limits() upp = joint.position_upper_limits() for j in range(joint.num_positions()): index = joint.position_start() + j description = joint.name() if joint.num_positions() > 1: description += f"[{j}]" slider = FloatSlider(value=positions[index], min=max(low[j], lower_limit[index]), max=min(upp[j], upper_limit[index]), step=resolution[index], continuous_update=continuous_update, description=description, style={'description_width': 'initial'}, layout=Layout(width=f"'{length}'")) slider.observe(partial(_slider_callback, index=index), names='value') display(slider) slider_widgets.append(slider) if floating_base: for bi in plant.GetFloatingBaseBodies(): body = plant.get_body(bi) index = body.floating_velocities_start() - plant.num_positions() pose = plant.GetFreeBodyPose(plant_context, body) vector_pose = np.concatenate( (RollPitchYaw(pose.rotation()).vector(), pose.translation())) relative_index = 0 for dof in ["roll", "pitch", "yaw", "x", "y", "z"]: slider = FloatSlider(min=lower_limit[index], max=upper_limit[index], value=vector_pose[relative_index], step=resolution[index], continuous_update=continuous_update, description=f"{body.name()}_{dof}", layout=Layout(width='90%')) slider.observe(partial(_slider_callback, index=relative_index, body=body), names='value') display(slider) slider_widgets.append(slider) index += 1 relative_index += 1 return slider_widgets