NVIDIA / warp

A Python framework for high performance GPU simulation and graphics

Home Page:https://nvidia.github.io/warp/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Franka URDF does not import properly

etaoxing opened this issue · comments

Edit:

Looks like the visual meshes are just flipped, the collision bodies are correct. Would be nice to have a flip_visual_attachments: Switch Meshes from Z-up left-handed system to Y-up Right-handed coordinate system. option for wp.sim.parse_urdf(), like in isaacgym:

Screenshot (collision shapes)...
example_franka_collision

franka_panda.urdf

Screenshot (visual meshes wrong)...
franka

xarm6_robot.urdf

Screenshot (works as intended)...
xarm

Code to repro...

Modified example_quadruped.py:

import math
import os

import numpy as np

import warp as wp
import warp.examples
import warp.sim
import warp.sim.render

wp.init()


# Taken from env/environment.py
def compute_env_offsets(num_envs, env_offset=(5.0, 0.0, 5.0), up_axis="Y"):
  # compute positional offsets per environment
  env_offset = np.array(env_offset)
  nonzeros = np.nonzero(env_offset)[0]
  num_dim = nonzeros.shape[0]
  if num_dim > 0:
      side_length = int(np.ceil(num_envs ** (1.0 / num_dim)))
      env_offsets = []
  else:
      env_offsets = np.zeros((num_envs, 3))
  if num_dim == 1:
      for i in range(num_envs):
          env_offsets.append(i * env_offset)
  elif num_dim == 2:
      for i in range(num_envs):
          d0 = i // side_length
          d1 = i % side_length
          offset = np.zeros(3)
          offset[nonzeros[0]] = d0 * env_offset[nonzeros[0]]
          offset[nonzeros[1]] = d1 * env_offset[nonzeros[1]]
          env_offsets.append(offset)
  elif num_dim == 3:
      for i in range(num_envs):
          d0 = i // (side_length * side_length)
          d1 = (i // side_length) % side_length
          d2 = i % side_length
          offset = np.zeros(3)
          offset[0] = d0 * env_offset[0]
          offset[1] = d1 * env_offset[1]
          offset[2] = d2 * env_offset[2]
          env_offsets.append(offset)
  env_offsets = np.array(env_offsets)
  min_offsets = np.min(env_offsets, axis=0)
  correction = min_offsets + (np.max(env_offsets, axis=0) - min_offsets) / 2.0
  if isinstance(up_axis, str):
      up_axis = "XYZ".index(up_axis.upper())
  correction[up_axis] = 0.0  # ensure the envs are not shifted below the ground plane
  env_offsets -= correction
  return env_offsets


class Example:
  def __init__(self, stage=None, num_envs=1, print_timers=True):
      self.num_envs = num_envs
      articulation_builder = wp.sim.ModelBuilder()
      wp.sim.parse_urdf(
          os.path.join(
              os.path.dirname(__file__),
              "assets/franka_description/robots/franka_panda.urdf",
              # "assets/xarm/xarm6_robot.urdf",
          ),
          articulation_builder,
          xform=wp.transform([0.0, 0.0, 0.0], wp.quat_from_axis_angle(wp.vec3(1.0, 0.0, 0.0), -math.pi * 0.5)),
          floating=False,
          density=1000,
          armature=0.1,
          stiffness=100,
          damping=40,
          # contact_ke=1.0e4,
          # contact_kd=1.0e2,
          # contact_kf=1.0e2,
          # contact_mu=1.0,
          # limit_ke=1.0e4,
          # limit_kd=1.0e1,
      )

      builder = wp.sim.ModelBuilder()

      self.sim_time = 0.0
      self.frame_dt = 1.0 / 60.0

      episode_duration = 0.5  # seconds
      self.episode_frames = int(episode_duration / self.frame_dt)

      self.sim_substeps = 16
      self.sim_dt = self.frame_dt / self.sim_substeps

      offsets = compute_env_offsets(num_envs)
      for i in range(num_envs):
          builder.add_builder(articulation_builder, xform=wp.transform(offsets[i], wp.quat_identity()))

          builder.joint_axis_mode = [wp.sim.JOINT_MODE_TARGET_POSITION] * len(builder.joint_axis_mode)
          builder.joint_q[-9:] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
          builder.joint_act[-9:] = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
          # builder.joint_q[-9:] = [0, 0.1963, 0, -2.6180, 0, 2.9416, 0.7854, 0.035, 0.035]
          # builder.joint_act[-9:] = [0, 0.1963, 0, -2.6180, 0, 2.9416, 0.7854, 0.035, 0.035]

          # builder.joint_q[-12:] = [0.2, 0.4, -0.6, -0.2, -0.4, 0.6, -0.2, 0.4, -0.6, 0.2, -0.4, 0.6]
          # builder.joint_act[-12:] = [0.2, 0.4, -0.6, -0.2, -0.4, 0.6, -0.2, 0.4, -0.6, 0.2, -0.4, 0.6]

      np.set_printoptions(suppress=True)
      # finalize model
      self.model = builder.finalize()
      self.model.ground = False
      # self.model.gravity = 0.0

      # self.model.joint_attach_ke = 16000.0
      # self.model.joint_attach_kd = 200.0

      # self.integrator = wp.sim.XPBDIntegrator()
      # self.integrator = wp.sim.SemiImplicitIntegrator()
      self.integrator = wp.sim.FeatherstoneIntegrator(self.model)

      self.renderer = None
      if stage:
          self.renderer = wp.sim.render.SimRenderer(self.model, stage)

      self.print_timers = print_timers

      self.state_0 = self.model.state()
      self.state_1 = self.model.state()

      wp.sim.eval_fk(self.model, self.model.joint_q, self.model.joint_qd, None, self.state_0)

      self.use_graph = wp.get_device().is_cuda
      self.graph = None
      if self.use_graph:
          with wp.ScopedCapture() as capture:
              self.simulate()
          self.graph = capture.graph

  def simulate(self):
      for _ in range(self.sim_substeps):
          self.state_0.clear_forces()
          wp.sim.collide(self.model, self.state_0)
          self.integrator.simulate(self.model, self.state_0, self.state_1, self.sim_dt)
          self.state_0, self.state_1 = self.state_1, self.state_0

  def step(self):
      with wp.ScopedTimer("step", active=True, print=self.print_timers):
          if self.use_graph:
              wp.capture_launch(self.graph)
          else:
              self.simulate()
      self.sim_time += self.frame_dt

  def render(self):
      if self.renderer is None:
          return

      with wp.ScopedTimer("render", active=True, print=self.print_timers):
          self.renderer.begin_frame(self.sim_time)
          self.renderer.render(self.state_0)
          self.renderer.end_frame()


if __name__ == "__main__":

  stage_path = "example_franka.usd"

  example = Example(stage_path, num_envs=1)

  for _ in range(example.episode_frames):
      example.step()
      example.render()

  if example.renderer:
      example.renderer.save()

Would also be nice if wp.sim.parse_urdf() automatically resolved filename="package://... paths!

Currently editing URDFs by hand:

<mesh filename="package://franka_description/meshes/collision/link0.obj"/>

->

<mesh filename="../meshes/collision/link0.obj"/>

Would also be nice to have a flag that loads torque ("effort) and velocity ("velocity") limits per joint from the URDF:

<joint>
  <limit lower="0", upper="0", effort="0", velocity="0">
</joint>

Hi @etaoxing,

Thank you for reporting these issues! We will have the URDF importer fixed in the next release. The visual meshes in the COLLADA format actually have the up_axis encoded, so by replacing this line with

m = trimesh.load(filename, force="mesh")

we can force trimesh to bake the transforms into the mesh when importing it.

We will also add support for the package:// handling in the next version.

Support for torque and velocity limits is on our ToDo list but will take a bit longer to implement for the different integrators.

I believe this was fixed by @eric-heiden in db752a3. Please reopen the issue if this is not the case. Thanks!