twanghcmut/backup-foundation-physics / scripts /fetch_robot_description.py
twanghcmut's picture
download
raw
27.5 kB
#!/usr/bin/env python3
"""CLI: fetch a Franka Panda + Robotiq 2F-85 URDF and meshes.
Two sources are supported via ``--source``:
``pointworld`` (default)
``NVlabs/PointWorld``'s ``assets/franka_description/``. This is the
source to actually use: its flange offset is correct and every mesh its
URDF references was actually committed upstream. See "why pointworld"
below.
``polymetis``
The original source this script fetched: ``facebookresearch/fairo``'s
``polymetis/polymetis/data/franka_panda_robotiq_85/``. Kept for
backwards compatibility; known-defective, see below.
Why pointworld replaced polymetis as the default
--------------------------------------------------
The polymetis URDF's ``panda_joint_ee`` places the flange at
``xyz="0 0 0.045"``, but the real Panda flange (``panda_link8``) sits at
``0.107``. That 62 mm shortfall isn't a guess: FK against DROID's own
recorded ``cartesian_position`` gave a residual of exactly ``[0, 0, 0.062] m``
with zero variance over 128 frames, and ``0.107 - 0.045 = 0.062`` -- an exact
match. Separately, its Robotiq gripper's visual ``.obj`` meshes 404 upstream
(see the ``polymetis`` fetch path's own docstring below for the substitution
this forces), and its visual/collision mesh scales are inconsistent (``0.1``
vs ``0.001``, and ``pad.stl`` at ``0.0001`` visual vs ``0.001`` collision --
a 10x fingertip error). pointworld's URDF has none of these problems: its
``panda_joint8`` origin is ``xyz="0 0 0.107"`` (correct), and every mesh it
references actually exists at the resolved URL -- no patching needed.
polymetis-specific quirks (only relevant to ``--source polymetis``)
---------------------------------------------------------------------
The upstream layout at ``facebookresearch/fairo``'s
``polymetis/polymetis/data/franka_panda_robotiq_85/`` ships the URDF next to
``meshes/visual``, ``meshes/collision`` and ``meshes/robotiq-2f`` *git
symlinks* rather than real directories -- they point at
``../franka_panda/meshes/{visual,collision}`` and
``../kuka_iiwa/meshes/robotiq-2f`` respectively. ``raw.githubusercontent.com``
serves a symlink's literal text content (the target path string), not the
file it points to, so a naive fetch of the URDF's own relative mesh paths
would download a handful of one-line text files instead of meshes. This
script resolves each symlink itself and downloads from the real upstream
path, while writing every file into a local flat layout that mirrors the
URDF's *unresolved* relative paths -- exactly what a URDF loader expects to
find sitting next to it.
Separately (and unrelated to the symlinks): the Robotiq gripper's *visual*
meshes (``meshes/robotiq-2f/visual/{base,driver,coupler,follower,
spring_link}.obj``) were never committed to the fairo repo at all -- fetching
them 404s even at their fully-resolved path. Only orphaned ``.mtl`` material
files and a texture set survive. This script writes a patched copy of the
URDF that substitutes the *collision* ``.stl`` geometry for the missing
visual meshes, which is exactly what fairo's own MJCF conversion of this
gripper does for the same reason -- see :func:`patch_urdf_visuals`.
"""
from __future__ import annotations
import argparse
import logging
import math
import sys
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from pathlib import Path, PurePosixPath
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
import requests # noqa: E402
from fpgm.utils.io import ( # noqa: E402
build_retrying_session,
download_with_resume,
ensure_dir,
human_bytes,
)
from fpgm.utils.logging import get_logger, setup_logging # noqa: E402
logger = get_logger(__name__)
FAIRO_REPO_URL = "https://github.com/facebookresearch/fairo"
FAIRO_DATA_ROOT = "polymetis/polymetis/data/"
FAIRO_RAW_BASE = f"https://raw.githubusercontent.com/facebookresearch/fairo/main/{FAIRO_DATA_ROOT}"
FAIRO_COMMITS_API = (
"https://api.github.com/repos/facebookresearch/fairo/commits"
f"?path={FAIRO_DATA_ROOT}franka_panda_robotiq_85/panda_robotiq_85.urdf&per_page=1"
)
#: Last-known-good resolution of the query above (2026-07-30), used if the API
#: call itself fails (rate limiting, no network egress to api.github.com, etc).
_FALLBACK_COMMIT_SHA = "768b118cb8f8ab7700504f40a74ed561f73b7289"
URDF_FILENAME = "panda_robotiq_85.urdf"
PATCHED_URDF_FILENAME = "panda_robotiq_85_patched.urdf"
DEFAULT_OUT_DIR = Path("third_party/robot_description/franka_panda_robotiq_85")
POINTWORLD_REPO_URL = "https://github.com/NVlabs/PointWorld"
POINTWORLD_DATA_ROOT = "assets/franka_description/"
POINTWORLD_RAW_BASE = f"https://raw.githubusercontent.com/NVlabs/PointWorld/main/{POINTWORLD_DATA_ROOT}"
POINTWORLD_URDF_FILENAME = "franka_panda_robotiq_2f85.urdf"
POINTWORLD_COMMITS_API = (
"https://api.github.com/repos/NVlabs/PointWorld/commits"
f"?path={POINTWORLD_DATA_ROOT}{POINTWORLD_URDF_FILENAME}&per_page=1"
)
#: Last-known-good resolution of the query above (2026-07-30), used if the API
#: call itself fails (rate limiting, no network egress to api.github.com, etc).
_POINTWORLD_FALLBACK_COMMIT_SHA = "8dff83df34cf80d2c69bec88ce3fd74afdb7e951"
DEFAULT_POINTWORLD_OUT_DIR = Path("third_party/robot_description/pointworld_franka_robotiq_2f85")
#: The real Panda flange (panda_link8) sits this far along +z from
#: panda_link7. This is the whole reason pointworld replaced polymetis as the
#: default source -- see the module docstring's "why pointworld" section for
#: the measured 62 mm discrepancy this catches.
EXPECTED_PANDA_LINK8_Z = 0.107
ARM_LINK_NAMES = tuple(f"link{i}" for i in range(8))
GRIPPER_COLLISION_PARTS = ("base", "coupler", "driver", "follower", "pad", "spring_link")
#: STL collision meshes are authored at millimetre scale (matching every
#: <collision> entry already in the URDF); the missing visual .obj meshes were
#: authored at a different scale (0.1) and the missing visual pad.stl at yet
#: another (0.0001). Blindly copying either onto a collision STL over-scales
#: the gripper 100x-1000x -- see :func:`patch_urdf_visuals`.
COLLISION_MESH_SCALE = "0.001 0.001 0.001"
@dataclass(frozen=True)
class AssetSpec:
"""One file to fetch: its resolved upstream URL and its local landing spot.
Attributes:
url: Fully-resolved raw.githubusercontent.com URL (symlinks already
resolved -- see module docstring).
local_relpath: Path relative to the output directory, matching the
*unresolved* relative path the URDF itself references.
"""
url: str
local_relpath: str
def build_asset_specs() -> list[AssetSpec]:
"""The URDF plus every mesh it (or its patched copy) needs, flat-laid-out."""
specs = [AssetSpec(FAIRO_RAW_BASE + f"franka_panda_robotiq_85/{URDF_FILENAME}", URDF_FILENAME)]
for link in ARM_LINK_NAMES:
specs.append(
AssetSpec(
f"{FAIRO_RAW_BASE}franka_panda/meshes/visual/{link}.dae",
f"meshes/visual/{link}.dae",
)
)
for link in ARM_LINK_NAMES:
specs.append(
AssetSpec(
f"{FAIRO_RAW_BASE}franka_panda/meshes/collision/{link}.stl",
f"meshes/collision/{link}.stl",
)
)
for part in GRIPPER_COLLISION_PARTS:
specs.append(
AssetSpec(
f"{FAIRO_RAW_BASE}kuka_iiwa/meshes/robotiq-2f/collision/{part}.stl",
f"meshes/robotiq-2f/collision/{part}.stl",
)
)
return specs
def download_assets(
out_dir: Path, specs: list[AssetSpec], *, force: bool, session: requests.Session
) -> None:
"""Download every spec into ``out_dir``, skipping already-cached files.
Args:
out_dir: Root of the flat local layout (see module docstring).
specs: Files to fetch.
force: Re-download even if a same-named file already sits at the
destination.
session: Shared retrying HTTP session.
Note:
This checks "already present with non-zero size" itself rather than
leaning solely on :func:`download_with_resume`'s own HEAD-vs-disk-size
cache check, because GitHub's CDN gzip-compresses ``text/plain``
responses (the ``.dae`` meshes and the URDF, unlike the
``application/octet-stream`` STLs): its ``Content-Length`` header is
the compressed size, while ``requests`` transparently decompresses
the body before it hits disk. Those two sizes never match, so relying
on that check alone would silently re-download every ``.dae`` and the
``.urdf`` on every single run.
"""
for spec in specs:
dest = out_dir / spec.local_relpath
if force and dest.exists():
dest.unlink()
if dest.exists() and dest.stat().st_size > 0:
logger.debug("already present, skipping: %s", dest)
continue
download_with_resume(spec.url, dest, session=session)
def collect_referenced_meshes(xml_text: str) -> set[str]:
"""Every ``<mesh filename="...">`` a URDF references, from <visual> or <collision>.
Deliberately parses rather than hardcodes a mesh list: pointworld's
``meshes/visual/`` directory ships 53 files (22.5 MB) covering this
assembly plus unrelated extras, and only a fraction of them are actually
referenced by this URDF. Reading the requirement off the URDF itself is
the only way to fetch exactly what's needed without either guessing or
downloading everything.
Primitive geometries (``<box>``, ``<cylinder>``, ``<sphere>``) have no
``filename`` attribute and aren't ``<mesh>`` elements at all, so they're
skipped implicitly by only iterating ``mesh`` tags.
Args:
xml_text: A URDF's full text.
Returns:
The set of relative mesh paths exactly as written in the URDF (no
normalisation of any kind), with duplicates collapsed.
"""
root = ET.fromstring(xml_text)
return {mesh.attrib["filename"] for mesh in root.iter("mesh") if "filename" in mesh.attrib}
def build_pointworld_mesh_specs(xml_text: str) -> list[AssetSpec]:
"""One :class:`AssetSpec` per mesh :func:`collect_referenced_meshes` finds in ``xml_text``."""
return [
AssetSpec(POINTWORLD_RAW_BASE + relpath, relpath)
for relpath in sorted(collect_referenced_meshes(xml_text))
]
def assert_single_camera_mount_link(xml_text: str) -> None:
"""Guard rail: pointworld's URDF defines ``camera_mount_link`` twice in the file.
The first definition (around line 705 of the upstream file) sits inside
an XML comment, so :mod:`xml.etree.ElementTree` -- which drops comments
-- never sees it; only the second, active definition is parsed. That's a
fragile invariant to rely on silently: if a future upstream edit
uncomments the first block, standard XML parsing would then see two
``<link name="camera_mount_link">`` elements and duplicate the geometry.
This asserts the invariant explicitly so that future breaks loudly
instead.
Args:
xml_text: The URDF's full text.
Raises:
RuntimeError: If the parsed tree contains anything other than
exactly one ``camera_mount_link`` link.
"""
root = ET.fromstring(xml_text)
matches = [
link for link in root.findall("link") if link.attrib.get("name") == "camera_mount_link"
]
if len(matches) != 1:
raise RuntimeError(
f"expected exactly one parsed camera_mount_link, found {len(matches)} -- "
"the upstream URDF has a second definition guarded by an XML comment; "
"see assert_single_camera_mount_link's docstring"
)
def assets_missing_or_empty(out_dir: Path, specs: list[AssetSpec]) -> list[str]:
"""Local relpaths from ``specs`` that are absent from disk or zero bytes."""
missing = []
for spec in specs:
dest = out_dir / spec.local_relpath
if not dest.exists() or dest.stat().st_size == 0:
missing.append(spec.local_relpath)
return missing
def patch_urdf_visuals(xml_text: str) -> str:
"""Repoint missing Robotiq visual meshes at their collision STL siblings.
Every ``<visual>`` mesh reference under ``meshes/robotiq-2f/visual/`` is
rewritten to the equivalent file under ``meshes/robotiq-2f/collision/``
(extension forced to ``.stl``, scale forced to
:data:`COLLISION_MESH_SCALE`). ``<collision>`` subtrees are never touched.
This single rule also covers ``pad``, whose upstream ``<visual>`` already
pointed at an ``.stl`` (not ``.obj``) at scale ``0.0001`` -- that file
still lives under the same missing ``visual/`` directory (see module
docstring) and was never downloaded, so it gets the same treatment as the
``.obj`` meshes: repoint to ``collision/pad.stl`` and correct the scale to
``0.001``. The original ``0.0001`` was calibrated for the missing visual
mesh's units, not the collision STL's -- keeping it would render the pad
roughly 10x too small relative to the rest of the (rescaled) gripper.
Args:
xml_text: The original URDF's full text.
Returns:
The patched URDF as a string, ready to write to disk.
"""
root = ET.fromstring(xml_text)
n_patched = 0
for visual in root.iter("visual"):
mesh = visual.find("geometry/mesh")
if mesh is None or "filename" not in mesh.attrib:
continue
filename = mesh.attrib["filename"]
if "robotiq-2f/visual/" not in filename:
continue
resolved = filename.replace("robotiq-2f/visual/", "robotiq-2f/collision/")
resolved = str(PurePosixPath(resolved).with_suffix(".stl"))
mesh.set("filename", resolved)
mesh.set("scale", COLLISION_MESH_SCALE)
n_patched += 1
logger.debug("patched %d robotiq visual mesh reference(s)", n_patched)
ET.indent(root, space=" ")
return ET.tostring(root, encoding="unicode") + "\n"
def write_patched_urdf(out_dir: Path) -> Path:
"""Read the original URDF and write :func:`patch_urdf_visuals`'s output beside it."""
original_path = out_dir / URDF_FILENAME
patched_path = out_dir / PATCHED_URDF_FILENAME
patched_path.write_text(patch_urdf_visuals(original_path.read_text()))
return patched_path
def resolve_commit_sha(session: requests.Session, api_url: str, fallback_sha: str) -> str:
"""Look up the commit that last touched the upstream URDF, for provenance.
Falls back to a last-known-good SHA (rather than failing the whole run)
if the GitHub API call itself doesn't succeed -- unauthenticated calls to
it are rate-limited and this isn't essential to actually fetching assets.
Args:
session: Shared retrying HTTP session.
api_url: A GitHub "list commits touching this path" API URL.
fallback_sha: Returned if the API call fails for any reason.
"""
try:
resp = session.get(api_url, timeout=30.0)
resp.raise_for_status()
commits = resp.json()
return str(commits[0]["sha"])
except (requests.RequestException, KeyError, IndexError, ValueError) as exc:
logger.warning("could not resolve commit sha from GitHub API (%s); using fallback", exc)
return fallback_sha
def write_provenance(out_dir: Path, specs: list[AssetSpec], commit_sha: str) -> None:
"""Record where every file came from and why the gripper visuals were substituted."""
lines = [
f"Source: {FAIRO_REPO_URL}",
f"Resolved commit: {commit_sha}",
f"Data root: {FAIRO_DATA_ROOT}",
"",
"Downloaded files:",
]
for spec in specs:
size = (out_dir / spec.local_relpath).stat().st_size
lines.append(f" {spec.local_relpath}\t{size} bytes ({human_bytes(size)})\t<- {spec.url}")
lines += [
"",
f"Wrote patched URDF: {PATCHED_URDF_FILENAME}",
"",
"Note: the Robotiq 2F-85 gripper's VISUAL meshes referenced by the "
"upstream URDF (meshes/robotiq-2f/visual/{base,driver,coupler,follower,"
"spring_link}.obj) were never committed to the fairo repo -- fetching "
"them returns HTTP 404, and only their orphaned .mtl material files "
"and a texture set survive. We substitute the COLLISION .stl geometry "
"for these visual meshes instead (rescaled to match STL units), which "
"is exactly what fairo's own MJCF conversion of this same gripper "
"does, for the same reason. See panda_robotiq_85_patched.urdf for the "
"rewritten <visual> mesh references; panda_robotiq_85.urdf is kept "
"unmodified so the two can be diffed.",
]
(out_dir / "PROVENANCE.txt").write_text("\n".join(lines) + "\n")
def write_pointworld_provenance(out_dir: Path, specs: list[AssetSpec], commit_sha: str) -> None:
"""Record where every file came from and why this source replaced polymetis."""
lines = [
f"Source: {POINTWORLD_REPO_URL}",
f"Resolved commit: {commit_sha}",
f"Data root: {POINTWORLD_DATA_ROOT}",
"",
"Downloaded files:",
]
for spec in specs:
size = (out_dir / spec.local_relpath).stat().st_size
lines.append(f" {spec.local_relpath}\t{size} bytes ({human_bytes(size)})\t<- {spec.url}")
lines += [
"",
"Why this source replaced facebookresearch/fairo's polymetis URDF "
"(third_party/robot_description/franka_panda_robotiq_85):",
" - polymetis's panda_joint_ee places the flange at xyz=\"0 0 0.045\", but "
"the real Panda flange (panda_link8) sits at 0.107 m. The 62 mm shortfall "
"is measured, not assumed: FK against DROID's own recorded "
"cartesian_position gave a residual of exactly [0, 0, 0.062] m with zero "
"variance over 128 frames, and 0.107 - 0.045 = 0.062 exactly.",
" - polymetis's Robotiq gripper visual .obj meshes 404 upstream -- they "
"were never committed to the fairo repo at all (only orphaned .mtl and "
"texture files survive). See franka_panda_robotiq_85/PROVENANCE.txt for "
"the collision-mesh substitution this forced.",
" - polymetis's visual/collision mesh scales are inconsistent (0.1 vs "
"0.001, and pad.stl at 0.0001 visual vs 0.001 collision -- a 10x "
"fingertip error) and its Robotiq mount joints lack an <origin>.",
f" This URDF's panda_joint8 origin is xyz=\"0 0 {EXPECTED_PANDA_LINK8_Z}\" -- "
"correct -- and every mesh it references was actually downloaded above, "
"so (unlike the polymetis path) no URDF patching is needed here.",
]
(out_dir / "PROVENANCE.txt").write_text("\n".join(lines) + "\n")
def verify_patched_urdf(patched_path: Path):
"""Load the patched URDF with meshes and confirm nothing failed to resolve.
yourdfpy doesn't raise when a referenced mesh file is missing -- it logs a
``"Can't find ..."`` warning on the ``yourdfpy.urdf`` logger and silently
drops that geometry from the scene. Catching that warning is therefore
the only reliable way to detect an unresolved mesh reference; a clean
load with zero visuals dropped would otherwise look identical to success.
Args:
patched_path: Path to the patched URDF to load.
Returns:
The loaded ``yourdfpy.URDF`` model.
Raises:
RuntimeError: If loading fails outright, or any visual mesh could not
be resolved.
"""
import yourdfpy
missing: list[str] = []
class _CaptureMissingMeshWarnings(logging.Handler):
def emit(self, record: logging.LogRecord) -> None:
message = record.getMessage()
if "Can't find" in message:
missing.append(message)
handler = _CaptureMissingMeshWarnings()
yourdfpy_logger = logging.getLogger("yourdfpy")
yourdfpy_logger.addHandler(handler)
try:
model = yourdfpy.URDF.load(str(patched_path), load_meshes=True, build_scene_graph=True)
except Exception as exc: # yourdfpy raises assorted exception types on bad input
raise RuntimeError(f"failed to load {patched_path}: {exc}") from exc
finally:
yourdfpy_logger.removeHandler(handler)
if missing:
raise RuntimeError(
f"{len(missing)} visual mesh(es) failed to resolve while loading {patched_path}: "
+ "; ".join(missing)
)
return model
def verify_pointworld_urdf(urdf_path: Path):
"""Load the URDF with meshes and confirm the flange offset is correct.
Unlike :func:`verify_patched_urdf`, this source has no missing meshes to
detect (see the module docstring) -- so nothing here watches for "Can't
find" warnings. What it does check is the entire reason this source
replaced polymetis as the default: ``panda_link8``'s parent joint must
place the flange at :data:`EXPECTED_PANDA_LINK8_Z`, not polymetis's
(measured-wrong) 0.045 m.
Args:
urdf_path: Path to the URDF to load.
Returns:
A ``(model, flange_z, finger_joint)`` tuple: the loaded
``yourdfpy.URDF``, the measured flange z-offset, and the
``finger_joint`` (the single joint every ``<mimic>`` in this URDF
drives).
Raises:
RuntimeError: If loading fails, ``panda_link8`` or its parent joint
is missing, its origin's z-offset isn't
:data:`EXPECTED_PANDA_LINK8_Z`, or ``finger_joint`` is missing.
"""
import yourdfpy
try:
model = yourdfpy.URDF.load(str(urdf_path), load_meshes=True, build_scene_graph=True)
except Exception as exc: # yourdfpy raises assorted exception types on bad input
raise RuntimeError(f"failed to load {urdf_path}: {exc}") from exc
if "panda_link8" not in model.link_map:
raise RuntimeError(f"panda_link8 not found among links loaded from {urdf_path}")
flange_joint = next((j for j in model.joint_map.values() if j.child == "panda_link8"), None)
if flange_joint is None or flange_joint.origin is None:
raise RuntimeError("no joint with an <origin> was found for panda_link8")
flange_z = float(flange_joint.origin[2, 3])
if not math.isclose(flange_z, EXPECTED_PANDA_LINK8_Z, abs_tol=1e-9):
raise RuntimeError(
f"panda_link8 flange z-offset is {flange_z}, expected "
f"{EXPECTED_PANDA_LINK8_Z} -- this assertion is the entire reason "
"pointworld replaced polymetis as the default source, see module docstring"
)
finger_joint = model.joint_map.get("finger_joint")
if finger_joint is None:
raise RuntimeError(f"finger_joint not found among joints loaded from {urdf_path}")
return model, flange_z, finger_joint
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--source",
choices=("pointworld", "polymetis"),
default="pointworld",
help="asset source: 'pointworld' (default; correct 0.107m flange offset) or "
"'polymetis' (fairo; kept for compatibility, has a measured 62mm flange bug)",
)
parser.add_argument(
"--out-dir",
type=Path,
default=None,
help="defaults to a source-specific directory under third_party/robot_description/",
)
parser.add_argument("--force", action="store_true", help="re-download files even if cached")
parser.add_argument(
"--skip-verify", action="store_true", help="skip the yourdfpy load-and-check step"
)
parser.add_argument("--log-level", default="INFO")
return parser.parse_args()
def _main_polymetis(args: argparse.Namespace, session: requests.Session) -> int:
out_dir = ensure_dir(args.out_dir or DEFAULT_OUT_DIR)
specs = build_asset_specs()
logger.info("fetching %d file(s) -> %s", len(specs), out_dir)
download_assets(out_dir, specs, force=args.force, session=session)
patched_path = write_patched_urdf(out_dir)
logger.info("wrote patched urdf -> %s", patched_path)
commit_sha = resolve_commit_sha(session, FAIRO_COMMITS_API, _FALLBACK_COMMIT_SHA)
write_provenance(out_dir, specs, commit_sha)
logger.info("wrote provenance -> %s", out_dir / "PROVENANCE.txt")
if args.skip_verify:
return 0
try:
model = verify_patched_urdf(patched_path)
except RuntimeError as exc:
logger.error("verification failed: %s", exc)
return 1
logger.info(
"verified: %d links, %d actuated joints: %s",
len(model.link_map),
model.num_actuated_joints,
model.actuated_joint_names,
)
return 0
def _main_pointworld(args: argparse.Namespace, session: requests.Session) -> int:
out_dir = ensure_dir(args.out_dir or DEFAULT_POINTWORLD_OUT_DIR)
urdf_dest = out_dir / POINTWORLD_URDF_FILENAME
urdf_spec = AssetSpec(POINTWORLD_RAW_BASE + POINTWORLD_URDF_FILENAME, POINTWORLD_URDF_FILENAME)
logger.info("fetching urdf -> %s", urdf_dest)
download_assets(out_dir, [urdf_spec], force=args.force, session=session)
xml_text = urdf_dest.read_text()
try:
assert_single_camera_mount_link(xml_text)
except RuntimeError as exc:
logger.error("urdf structure check failed: %s", exc)
return 1
mesh_specs = build_pointworld_mesh_specs(xml_text)
logger.info("urdf references %d mesh file(s); fetching -> %s", len(mesh_specs), out_dir)
download_assets(out_dir, mesh_specs, force=args.force, session=session)
all_specs = [urdf_spec, *mesh_specs]
missing = assets_missing_or_empty(out_dir, all_specs)
if missing:
logger.error(
"%d referenced mesh(es) missing or empty after download: %s",
len(missing),
", ".join(missing),
)
return 1
mesh_bytes = sum((out_dir / spec.local_relpath).stat().st_size for spec in mesh_specs)
logger.info(
"downloaded %d referenced mesh(es), %s total (%d file(s) overall)",
len(mesh_specs),
human_bytes(mesh_bytes),
len(all_specs),
)
commit_sha = resolve_commit_sha(
session, POINTWORLD_COMMITS_API, _POINTWORLD_FALLBACK_COMMIT_SHA
)
write_pointworld_provenance(out_dir, all_specs, commit_sha)
logger.info("wrote provenance -> %s", out_dir / "PROVENANCE.txt")
if args.skip_verify:
return 0
try:
model, flange_z, finger_joint = verify_pointworld_urdf(urdf_dest)
except RuntimeError as exc:
logger.error("verification failed: %s", exc)
return 1
logger.info(
"verified: %d links, %d actuated joints: %s",
len(model.link_map),
model.num_actuated_joints,
model.actuated_joint_names,
)
logger.info(
"panda_link8 flange z = %.3f m (expected %.3f m) -- the fix this source exists for",
flange_z,
EXPECTED_PANDA_LINK8_Z,
)
limit = finger_joint.limit
logger.info(
"finger_joint limits: lower=%s upper=%s effort=%s velocity=%s",
limit.lower if limit else None,
limit.upper if limit else None,
limit.effort if limit else None,
limit.velocity if limit else None,
)
return 0
def main() -> int:
args = parse_args()
setup_logging(args.log_level)
session = build_retrying_session()
if args.source == "polymetis":
return _main_polymetis(args, session)
return _main_pointworld(args, session)
if __name__ == "__main__":
raise SystemExit(main())

Xet Storage Details

Size:
27.5 kB
·
Xet hash:
75d0d0e401cf58d224326834a8ba07ac1a13ca8ac9fba4879d708f0c41e465a6

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.