diff --git a/dimos/manipulation/grasping/grasp_gen_spec.py b/dimos/manipulation/grasping/grasp_gen_spec.py index 37c81c85bc..07e3882a9a 100644 --- a/dimos/manipulation/grasping/grasp_gen_spec.py +++ b/dimos/manipulation/grasping/grasp_gen_spec.py @@ -14,14 +14,10 @@ from typing import Protocol -from dimos.msgs.geometry_msgs.PoseArray import PoseArray +from dimos.msgs.manipulation_msgs.GraspCandidateArray import GraspCandidateArray from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 from dimos.spec.utils import Spec class GraspGenSpec(Spec, Protocol): - def generate_grasps( - self, - pointcloud: PointCloud2, - scene_pointcloud: PointCloud2 | None = None, - ) -> PoseArray | None: ... + def propose_grasps(self, object_pointcloud: PointCloud2) -> GraspCandidateArray: ... diff --git a/dimos/manipulation/grasping/grasp_gen_x.py b/dimos/manipulation/grasping/grasp_gen_x.py new file mode 100644 index 0000000000..0107da1a32 --- /dev/null +++ b/dimos/manipulation/grasping/grasp_gen_x.py @@ -0,0 +1,185 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Import-safe DimOS adapter for GraspGenX grasp proposals.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Annotated, Any, Literal, TypeAlias + +import numpy as np +from pydantic import Field, FiniteFloat, field_validator + +from dimos.core.core import rpc +from dimos.core.module import Module, ModuleConfig +from dimos.manipulation.grasping.grasp_gen_spec import GraspGenSpec +from dimos.msgs.geometry_msgs.Pose import Pose +from dimos.msgs.geometry_msgs.Quaternion import Quaternion +from dimos.msgs.geometry_msgs.Vector3 import Vector3 +from dimos.msgs.manipulation_msgs.GraspCandidate import GraspCandidate +from dimos.msgs.manipulation_msgs.GraspCandidateArray import GraspCandidateArray +from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 +from dimos.msgs.std_msgs.Header import Header +from dimos.protocol.service.spec import BaseConfig + +if TYPE_CHECKING: + from dimos.manipulation.grasping.grasp_gen_x_runtime import GraspGenXRuntime + +GRASPGENX_MODEL_REPO = "adithyamurali/GraspGenXModel" +GRASPGENX_MODEL_REVISION = "7c834043c11a11417e31d6d5ea9355801e40a2c1" +GRASPGENX_MODEL_VERSION = "release" + +BoundedExtent = Annotated[FiniteFloat, Field(gt=0.0, le=0.5)] +BoundedOffset = Annotated[FiniteFloat, Field(ge=-0.5, le=0.5)] +PositiveCount = Annotated[int, Field(gt=0, strict=True)] +SweepExtents: TypeAlias = tuple[BoundedExtent, BoundedExtent, BoundedExtent] +SweepOffset: TypeAlias = tuple[BoundedOffset, BoundedOffset, BoundedOffset] +Vector4: TypeAlias = tuple[FiniteFloat, FiniteFloat, FiniteFloat, FiniteFloat] +RigidTransform: TypeAlias = tuple[Vector4, Vector4, Vector4, Vector4] +GripperFamily: TypeAlias = Literal["parallel_2f", "revolute_2f", "revolute_3f"] + +IDENTITY_TRANSFORM: RigidTransform = ( + (1.0, 0.0, 0.0, 0.0), + (0.0, 1.0, 0.0, 0.0), + (0.0, 0.0, 1.0, 0.0), + (0.0, 0.0, 0.0, 1.0), +) + + +class SweepVolumeGripperConfig(BaseConfig): + """Axis-aligned open and half-open sweep-volume description.""" + + extents_open: SweepExtents + offset_open: SweepOffset + extents_half_open: SweepExtents + offset_half_open: SweepOffset + fingertip_depth: BoundedExtent + family: GripperFamily = "parallel_2f" + + +class GraspGenXConfig(ModuleConfig): + """GraspGenX deployment settings, serializable by DimOS blueprints.""" + + gripper: SweepVolumeGripperConfig + grasp_frame_to_tcp: RigidTransform = IDENTITY_TRANSFORM + max_candidates: PositiveCount = 100 + + # Relational matrix properties cannot be expressed through scalar Field constraints. + @field_validator("grasp_frame_to_tcp") + @classmethod + def _validate_rigid_transform(cls, value: RigidTransform) -> RigidTransform: + matrix = np.asarray(value, dtype=float) + if not np.allclose(matrix[3], [0.0, 0.0, 0.0, 1.0], atol=1e-7): + raise ValueError("grasp_frame_to_tcp must be homogeneous") + rotation = matrix[:3, :3] + if not np.allclose(rotation.T @ rotation, np.eye(3), atol=1e-6) or not np.isclose( + np.linalg.det(rotation), 1.0, atol=1e-6 + ): + raise ValueError("grasp_frame_to_tcp rotation must be orthonormal with determinant +1") + return value + + +class GraspGenXError(RuntimeError): + """Base error for model loading and inference failures.""" + + +def _create_runtime(config: GraspGenXConfig) -> GraspGenXRuntime: + # This import is the intentional first-use boundary for the optional GPU runtime. + from dimos.manipulation.grasping.grasp_gen_x_runtime import GraspGenXRuntime + + return GraspGenXRuntime(config) + + +class GraspGenXModule(Module, GraspGenSpec): + """Direct adapter whose optional runtime is loaded synchronously by ``start``.""" + + dedicated_worker = True + config: GraspGenXConfig # type: ignore[assignment] + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self._runtime: GraspGenXRuntime | None = None + + @rpc + def start(self) -> None: + super().start() + if self._runtime is not None: + return + try: + self._runtime = _create_runtime(self.config) + except Exception as exc: + raise GraspGenXError("failed to initialize GraspGenX") from exc + + @rpc + def stop(self) -> None: + self._runtime = None + super().stop() + + @rpc + def propose_grasps(self, object_pointcloud: PointCloud2) -> GraspCandidateArray: + if self._runtime is None: + raise GraspGenXError("GraspGenX module has not been started") + if object_pointcloud.ts is None: + raise ValueError("object pointcloud must have a timestamp") + if not object_pointcloud.frame_id: + raise ValueError("object pointcloud frame_id must not be empty") + + points = object_pointcloud.points_f32() + if points.ndim != 2 or points.shape[1] != 3 or len(points) == 0: + raise ValueError("object pointcloud must contain at least one XYZ point") + if not np.all(np.isfinite(points)): + raise ValueError("object pointcloud XYZ values must be finite floats in metres") + + try: + poses, scores = self._runtime.infer(points) + except Exception as exc: + raise GraspGenXError("GraspGenX inference failed") from exc + scores = scores.reshape(-1) + + if poses.size == 0 and scores.size == 0: + return GraspCandidateArray( + Header(float(object_pointcloud.ts), object_pointcloud.frame_id), + [], + ) + if poses.shape != (len(scores), 4, 4): + raise ValueError("backend poses must have shape (N, 4, 4)") + if not np.all(np.isfinite(poses)) or not np.all(np.isfinite(scores)): + raise ValueError("backend returned non-finite poses or scores") + if not np.allclose(poses[:, 3, :], np.array([0.0, 0.0, 0.0, 1.0]), atol=1e-7): + raise ValueError("backend poses must be homogeneous") + rotations = poses[:, :3, :3] + if not np.allclose(np.einsum("nij,nkj->nik", rotations, rotations), np.eye(3), atol=1e-5): + raise ValueError("backend poses must have orthonormal rotations") + if not np.allclose(np.linalg.det(rotations), 1.0, atol=1e-5): + raise ValueError("backend poses must have proper rotations") + + tcp_poses = poses @ np.asarray(self.config.grasp_frame_to_tcp) + order = np.argsort(-scores, kind="stable")[: self.config.max_candidates] + candidates = [ + GraspCandidate(self._pose_from_matrix(tcp_poses[index]), float(scores[index])) + for index in order + ] + return GraspCandidateArray( + Header(float(object_pointcloud.ts), object_pointcloud.frame_id), + candidates, + ) + + @staticmethod + def _pose_from_matrix(matrix: np.ndarray) -> Pose: + return Pose( + { + "position": Vector3(matrix[:3, 3]), + "orientation": Quaternion.from_rotation_matrix(matrix[:3, :3]), + } + ) diff --git a/dimos/manipulation/grasping/grasp_gen_x_runtime.py b/dimos/manipulation/grasping/grasp_gen_x_runtime.py new file mode 100644 index 0000000000..34a76c7f2e --- /dev/null +++ b/dimos/manipulation/grasping/grasp_gen_x_runtime.py @@ -0,0 +1,98 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""First-use GraspGenX runtime with top-level optional dependency imports.""" + +from __future__ import annotations + +import os +from pathlib import Path + +from huggingface_hub import snapshot_download +import numpy as np + +from dimos.manipulation.grasping.grasp_gen_x import ( + GRASPGENX_MODEL_REPO, + GRASPGENX_MODEL_REVISION, + GRASPGENX_MODEL_VERSION, + GraspGenXConfig, +) + +_snapshot_root = Path( + snapshot_download( + repo_id=GRASPGENX_MODEL_REPO, + revision=GRASPGENX_MODEL_REVISION, + allow_patterns=[ + f"{GRASPGENX_MODEL_VERSION}/gen/*", + f"{GRASPGENX_MODEL_VERSION}/dis/*", + ], + ) +).resolve() +_checkpoint_root = _snapshot_root / GRASPGENX_MODEL_VERSION +_gen_dir = _checkpoint_root / "gen" +_dis_dir = _checkpoint_root / "dis" +if not _gen_dir.is_dir() or not _dis_dir.is_dir(): + raise FileNotFoundError( + f"GraspGenX checkpoint must contain release/gen and release/dis: {_snapshot_root}" + ) + +# Upstream's package initializer otherwise performs Git clones while importing. The +# sweep-volume runtime does not consume named gripper assets, so the existing snapshot +# directory also suppresses that unused asset clone. +os.environ["GRASPGENX_CHECKPOINT_DIR"] = str(_snapshot_root) +os.environ["GRASPGENX_GRIPPER_CFG_DIR"] = str(_snapshot_root) + +from graspgenx.grasp_server import ( + SWEEP_VOLUME_ONLY_BACKBONES, + GraspGenXSampler, +) +from graspgenx.utils.checkpoint_io import load_model_cfg +from graspgenx.x_grippers import make_sweep_volume_gripper_info + +_GRIPPER_TYPES = { + "parallel_2f": 0, + "revolute_2f": 1, + "revolute_3f": 2, +} + + +class GraspGenXRuntime: + """Loaded GraspGenX sampler and exact tensor conversion boundary.""" + + def __init__(self, config: GraspGenXConfig) -> None: + model_config = load_model_cfg(_gen_dir, _dis_dir, gen_pth=None, dis_pth=None) + for component in ("diffusion", "discriminator"): + backbone = getattr(model_config, component).gripper_backbone + if backbone not in SWEEP_VOLUME_ONLY_BACKBONES: + raise ValueError( + f"GraspGenX {component}.gripper_backbone={backbone!r} " + "requires an asset-backed gripper" + ) + gripper_info = make_sweep_volume_gripper_info( + extents_open=config.gripper.extents_open, + offset_open=config.gripper.offset_open, + extents_mid=config.gripper.extents_half_open, + offset_mid=config.gripper.offset_half_open, + gripper_type=_GRIPPER_TYPES[config.gripper.family], + fingertip_depth=config.gripper.fingertip_depth, + ) + self._sampler = GraspGenXSampler(model_config, gripper_info=gripper_info) + + def infer(self, points: np.ndarray) -> tuple[np.ndarray, np.ndarray]: + """Run inference and copy the known torch tensors to CPU NumPy arrays.""" + poses, scores = GraspGenXSampler.run_inference(points, self._sampler) + return ( + poses.detach().cpu().numpy(), + scores.detach().cpu().numpy(), + ) diff --git a/dimos/manipulation/grasping/test_grasp_gen_x.py b/dimos/manipulation/grasping/test_grasp_gen_x.py new file mode 100644 index 0000000000..e503214509 --- /dev/null +++ b/dimos/manipulation/grasping/test_grasp_gen_x.py @@ -0,0 +1,289 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Hermetic tests for the import-safe GraspGenX adapter.""" + +from __future__ import annotations + +import inspect +import subprocess +import sys +from typing import Any + +import numpy as np +import pytest +from pytest_mock import MockerFixture + +from dimos.manipulation.grasping.grasp_gen_spec import GraspGenSpec +import dimos.manipulation.grasping.grasp_gen_x as grasp_gen_x +from dimos.manipulation.grasping.grasp_gen_x import ( + GraspGenXConfig, + GraspGenXError, + GraspGenXModule, +) +from dimos.msgs.geometry_msgs.Pose import Pose +from dimos.msgs.manipulation_msgs.GraspCandidate import GraspCandidate +from dimos.msgs.manipulation_msgs.GraspCandidateArray import GraspCandidateArray +from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 +from dimos.msgs.std_msgs.Header import Header + + +def config(**overrides: object) -> GraspGenXConfig: + values: dict[str, object] = { + "gripper": { + "extents_open": (0.1, 0.1, 0.1), + "offset_open": (0.0, 0.0, 0.0), + "extents_half_open": (0.1, 0.1, 0.1), + "offset_half_open": (0.0, 0.0, 0.0), + "fingertip_depth": 0.1, + }, + } + values.update(overrides) + return GraspGenXConfig(**values) # type: ignore[arg-type] + + +def module_args(value: GraspGenXConfig | None = None) -> dict[str, Any]: + return (value or config()).model_dump(exclude={"rpc_transport", "tf_transport", "g"}) + + +def cloud(points: np.ndarray | None = None) -> PointCloud2: + xyz = np.zeros((1, 3), dtype=np.float32) if points is None else points + return PointCloud2.from_numpy(xyz, frame_id="camera", timestamp=12.5) + + +def test_public_adapter_import_does_not_load_optional_runtime() -> None: + result = subprocess.run( + [ + sys.executable, + "-c", + ( + "import sys; " + "import dimos.manipulation.grasping.grasp_gen_x; " + "assert 'dimos.manipulation.grasping.grasp_gen_x_runtime' not in sys.modules" + ), + ], + check=False, + capture_output=True, + text=True, + ) + + assert result.returncode == 0, result.stderr + + +@pytest.fixture +def runtime(mocker: MockerFixture) -> Any: + create_runtime = mocker.patch.object(grasp_gen_x, "_create_runtime") + instance = create_runtime.return_value + instance.infer.return_value = ( + np.repeat(np.eye(4, dtype=np.float32)[None], 1, axis=0), + np.asarray([0.5], dtype=np.float32), + ) + return create_runtime + + +def test_messages_round_trip_empty_and_score() -> None: + value = GraspCandidateArray(Header(3.0, "camera"), [GraspCandidate(Pose(1, 2, 3), 0.25)]) + decoded = GraspCandidateArray.decode(value.encode()) + + assert decoded.header.frame_id == "camera" + assert decoded.header.timestamp == pytest.approx(3.0) + assert decoded.candidates[0].score == pytest.approx(0.25) + assert ( + GraspCandidateArray.decode( + GraspCandidateArray(Header(3.0, "camera"), []).encode() + ).candidates + == [] + ) + + +def test_spec_signature() -> None: + signature = inspect.signature(GraspGenSpec.propose_grasps) + + assert list(signature.parameters) == ["self", "object_pointcloud"] + assert signature.parameters["object_pointcloud"].annotation.__name__ == "PointCloud2" + assert signature.return_annotation is GraspCandidateArray + + +@pytest.mark.parametrize( + ("field", "value"), + [ + ("family", "unsupported"), + ("extents_open", (0.1, 0.1)), + ("extents_open", (0.0, 0.1, 0.1)), + ("extents_open", (0.6, 0.1, 0.1)), + ("offset_open", (0.6, 0.0, 0.0)), + ("offset_open", (np.nan, 0.0, 0.0)), + ("fingertip_depth", 0.0), + ], +) +def test_gripper_constraints_are_declared_by_fields(field: str, value: object) -> None: + gripper = config().gripper.model_dump() + + with pytest.raises(ValueError): + config(gripper={**gripper, field: value}) + + +@pytest.mark.parametrize("value", [0, -1, True]) +def test_candidate_limit_is_a_strict_positive_integer(value: object) -> None: + with pytest.raises(ValueError): + config(max_candidates=value) + + +def test_rigid_transform_relational_validation() -> None: + with pytest.raises(ValueError, match="orthonormal"): + config( + grasp_frame_to_tcp=( + (2.0, 0.0, 0.0, 0.0), + (0.0, 1.0, 0.0, 0.0), + (0.0, 0.0, 1.0, 0.0), + (0.0, 0.0, 0.0, 1.0), + ) + ) + + +def test_start_is_synchronous_and_idempotent(runtime: Any) -> None: + module = GraspGenXModule(**module_args()) + try: + module.start() + module.start() + + runtime.assert_called_once_with(module.config) + assert len(module.propose_grasps(cloud())) == 1 + finally: + module.stop() + + +def test_start_failure_is_explicit(runtime: Any) -> None: + runtime.side_effect = RuntimeError("CUDA unavailable") + module = GraspGenXModule(**module_args()) + try: + with pytest.raises(GraspGenXError, match="initialize"): + module.start() + finally: + module.stop() + + +def test_adapter_sorts_stably_truncates_and_applies_tcp_transform(runtime: Any) -> None: + poses = np.repeat(np.eye(4, dtype=np.float32)[None], 3, axis=0) + poses[:, 0, 3] = [1.0, 2.0, 3.0] + runtime.return_value.infer.return_value = ( + poses, + np.asarray([0.5, 0.5, 0.9], dtype=np.float32), + ) + cfg = config( + max_candidates=2, + grasp_frame_to_tcp=( + (0.0, -1.0, 0.0, 10.0), + (1.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 1.0, 0.0), + (0.0, 0.0, 0.0, 1.0), + ), + ) + module = GraspGenXModule(**module_args(cfg)) + try: + module.start() + result = module.propose_grasps(cloud()) + + assert [candidate.score for candidate in result] == pytest.approx([0.9, 0.5]) + assert [candidate.pose.position.x for candidate in result] == pytest.approx([13.0, 11.0]) + assert result.header.frame_id == "camera" + assert result.header.timestamp == pytest.approx(12.5) + finally: + module.stop() + + +def test_empty_backend_result_preserves_input_header(runtime: Any) -> None: + runtime.return_value.infer.return_value = ( + np.empty((0, 4, 4), dtype=np.float32), + np.empty(0, dtype=np.float32), + ) + module = GraspGenXModule(**module_args()) + try: + module.start() + result = module.propose_grasps(cloud()) + + assert result.header.frame_id == "camera" + assert result.header.timestamp == pytest.approx(12.5) + assert result.candidates == [] + finally: + module.stop() + + +@pytest.mark.parametrize( + "points", + [ + np.array([[np.nan, 0.0, 0.0]], dtype=np.float32), + np.empty((0, 3), dtype=np.float32), + np.zeros((2, 2), dtype=np.float32), + ], +) +def test_invalid_cloud_points_are_rejected(runtime: Any, points: np.ndarray) -> None: + module = GraspGenXModule(**module_args()) + try: + module.start() + with pytest.raises(ValueError, match="pointcloud|XYZ"): + module.propose_grasps(cloud(points)) + runtime.return_value.infer.assert_not_called() + finally: + module.stop() + + +@pytest.mark.parametrize( + "backend", + [ + (np.ones((2, 4, 4)), np.ones(1)), + (np.full((1, 4, 4), np.nan), np.ones(1)), + (np.ones((1, 4, 4)), np.array([np.inf])), + ], +) +def test_invalid_backend_outputs_are_rejected( + runtime: Any, backend: tuple[np.ndarray, np.ndarray] +) -> None: + runtime.return_value.infer.return_value = backend + module = GraspGenXModule(**module_args()) + try: + module.start() + with pytest.raises(ValueError): + module.propose_grasps(cloud()) + finally: + module.stop() + + +def test_inference_failure_is_wrapped(runtime: Any) -> None: + runtime.return_value.infer.side_effect = RuntimeError("backend") + module = GraspGenXModule(**module_args()) + try: + module.start() + with pytest.raises(GraspGenXError, match="inference"): + module.propose_grasps(cloud()) + finally: + module.stop() + + +def test_not_started_and_missing_metadata_are_rejected(runtime: Any) -> None: + module = GraspGenXModule(**module_args()) + missing_frame = cloud() + missing_frame.frame_id = "" + missing_timestamp = cloud() + missing_timestamp.ts = None + try: + with pytest.raises(GraspGenXError, match="not been started"): + module.propose_grasps(cloud()) + module.start() + with pytest.raises(ValueError, match="frame_id"): + module.propose_grasps(missing_frame) + with pytest.raises(ValueError, match="timestamp"): + module.propose_grasps(missing_timestamp) + finally: + module.stop() diff --git a/dimos/manipulation/pick_and_place_module.py b/dimos/manipulation/pick_and_place_module.py index c788277b94..6977e306a0 100644 --- a/dimos/manipulation/pick_and_place_module.py +++ b/dimos/manipulation/pick_and_place_module.py @@ -22,14 +22,22 @@ from __future__ import annotations +from collections import Counter +from dataclasses import dataclass, field +from enum import Enum import math +import threading import time -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Literal + +import numpy as np +from pydantic import Field, FiniteFloat, model_validator from dimos.agents.annotation import skill from dimos.agents.skill_result import SkillResult from dimos.core.core import rpc from dimos.core.stream import In +from dimos.manipulation.grasping.grasp_gen_spec import GraspGenSpec from dimos.manipulation.manipulation_module import ( ManipulationModule, ManipulationModuleConfig, @@ -38,10 +46,16 @@ from dimos.msgs.geometry_msgs.Pose import Pose from dimos.msgs.geometry_msgs.Quaternion import Quaternion from dimos.msgs.geometry_msgs.Vector3 import Vector3 +from dimos.msgs.manipulation_msgs.GraspCandidate import GraspCandidate from dimos.perception.experimental.object import ( Object as DetObject, ) +from dimos.perception.experimental.object_scene_registration_spec import ( + ObjectSceneRegistrationSpec, +) +from dimos.protocol.service.spec import BaseConfig from dimos.utils.logging_config import setup_logger +from dimos.utils.transform_utils import offset_distance if TYPE_CHECKING: from dimos.msgs.geometry_msgs.PoseArray import PoseArray @@ -62,9 +76,102 @@ _TALL_OBJECT_MIN_HEIGHT = 0.06 +class GraspVerificationConfig(BaseConfig): + """Robot-specific gripper closure verification settings.""" + + enabled: bool = False + open_position: FiniteFloat = 0.85 + closed_position: FiniteFloat = 0.0 + held_threshold: FiniteFloat = 0.02 + timeout: FiniteFloat = Field(default=2.0, gt=0.0) + poll_interval: FiniteFloat = Field(default=0.05, gt=0.0) + + @model_validator(mode="after") + def _validate_threshold(self) -> GraspVerificationConfig: + low = min(self.open_position, self.closed_position) + high = max(self.open_position, self.closed_position) + if self.open_position == self.closed_position: + raise ValueError("gripper open_position and closed_position must differ") + if not low < self.held_threshold < high: + raise ValueError("held_threshold must lie between open_position and closed_position") + if self.poll_interval > self.timeout: + raise ValueError("poll_interval must not exceed timeout") + return self + + class PickAndPlaceModuleConfig(ManipulationModuleConfig): """Configuration for PickAndPlaceModule.""" + heuristic_grasp_fallback: bool = False + planning_frame: str = "world" + max_object_pointcloud_age: FiniteFloat = Field(default=10.0, gt=0.0) + max_grasp_candidates_to_check: int = Field(default=5, gt=0) + grasp_pre_grasp_offset: FiniteFloat | None = Field(default=None, gt=0.0) + grasp_retreat_offset: FiniteFloat | None = Field(default=None, gt=0.0) + grasp_approach_vector: tuple[FiniteFloat, FiniteFloat, FiniteFloat] = (0.0, 0.0, -1.0) + grasp_verification: GraspVerificationConfig = Field(default_factory=GraspVerificationConfig) + + @model_validator(mode="after") + def _validate_grasp_pipeline(self) -> PickAndPlaceModuleConfig: + if not self.planning_frame.strip(): + raise ValueError("planning_frame must not be empty") + vector = np.asarray(self.grasp_approach_vector, dtype=float) + if not np.isclose(np.linalg.norm(vector), 1.0, atol=1e-6): + raise ValueError("grasp_approach_vector must be a unit vector") + return self + + +class _PickPhase(str, Enum): + RESOLVE = "RESOLVE" + PROPOSE = "PROPOSE" + SELECT = "SELECT" + PREPARE = "PREPARE" + APPROACH = "APPROACH" + GRASP = "GRASP" + CLOSE = "CLOSE" + VERIFY = "VERIFY" + RETREAT = "RETREAT" + DONE = "DONE" + + +class _CandidateRejection(str, Enum): + INVALID = "invalid" + PRE_GRASP_INFEASIBLE = "pre_grasp_infeasible" + GRASP_INFEASIBLE = "grasp_infeasible" + RETREAT_INFEASIBLE = "retreat_infeasible" + + +@dataclass(frozen=True) +class _FeasibleGrasp: + candidate: GraspCandidate + rank: int + pre_grasp_pose: Pose + retreat_pose: Pose + + +@dataclass(frozen=True) +class _GraspVerification: + held: bool + position: float | None + detail: str + + +@dataclass +class _PickTransaction: + object_id: str = "" + object_name: str = "" + proposal_source: Literal["grasp_provider", "heuristic"] = "grasp_provider" + phase: _PickPhase = _PickPhase.RESOLVE + selected: _FeasibleGrasp | None = None + rejections: Counter[str] = field(default_factory=Counter) + gripper_closed: bool = False + + +class _PickPipelineError(RuntimeError): + def __init__(self, code: ManipulationSkillError, message: str) -> None: + super().__init__(message) + self.code = code + class PickAndPlaceModule(ManipulationModule): """Manipulation module with perception integration and pick-and-place skills. @@ -76,6 +183,8 @@ class PickAndPlaceModule(ManipulationModule): """ config: PickAndPlaceModuleConfig + _object_scene: ObjectSceneRegistrationSpec | None = None + _grasp_generator: GraspGenSpec | None = None # Input: Objects from perception (for obstacle integration) objects: In[list[DetObject]] @@ -90,6 +199,7 @@ def __init__(self, **kwargs: Any) -> None: # The live detection cache is volatile (labels change every frame), # so pick/place use this stable snapshot instead. self._detection_snapshot: list[DetObject] = [] + self._pick_guard = threading.Lock() @rpc def start(self) -> None: @@ -177,7 +287,12 @@ def generate_grasps( "GraspGen Docker support removed; see issue #1266 for re-implementation as NativeModule subclass" ) - def _compute_pre_grasp_pose(self, grasp_pose: Pose, offset: float = 0.10) -> Pose: + def _compute_pre_grasp_pose( + self, + grasp_pose: Pose, + offset: float = 0.10, + approach_vector: Vector3 | None = None, + ) -> Pose: """Compute a pre-grasp pose offset along the approach direction (local -Z). Args: @@ -187,9 +302,11 @@ def _compute_pre_grasp_pose(self, grasp_pose: Pose, offset: float = 0.10) -> Pos Returns: Pre-grasp pose offset from the grasp pose """ - from dimos.utils.transform_utils import offset_distance - - return offset_distance(grasp_pose, offset) + return offset_distance( + grasp_pose, + offset, + approach_vector if approach_vector is not None else Vector3(0.0, 0.0, -1.0), + ) def _find_object_in_detections( self, object_name: str, object_id: str | None = None @@ -224,10 +341,19 @@ def _find_object_in_detections( logger.warning(f"Ambiguous object_id prefix '{object_id}' matches {ids}") return None - # Second pass: match by name - for det in self._detection_snapshot: - if object_name.lower() in det.name.lower() or det.name.lower() in object_name.lower(): - return det + # Second pass: require a unique name match. + normalized = object_name.casefold() + name_matches = [ + det + for det in self._detection_snapshot + if normalized in det.name.casefold() or det.name.casefold() in normalized + ] + if len(name_matches) == 1: + return name_matches[0] + if len(name_matches) > 1: + ids = [det.object_id for det in name_matches] + logger.warning("Ambiguous object name", object_name=object_name, object_ids=ids) + return None available = [det.name for det in self._detection_snapshot] logger.warning(f"Object '{object_name}' not found in snapshot. Available: {available}") @@ -454,7 +580,8 @@ def scan_objects( for det in detections: c = det.center lines.append( - f" - {det.name}: ({c.x:.3f}, {c.y:.3f}, {c.z:.3f}) [{det.detections_count} views]" + f" - {det.name} [id={det.object_id[:8]}]: " + f"({c.x:.3f}, {c.y:.3f}, {c.z:.3f}) [{det.detections_count} views]" ) if obstacles: @@ -462,6 +589,282 @@ def scan_objects( return SkillResult.ok("\n".join(lines)) + def _require_pick_object(self, object_name: str, object_id: str | None) -> DetObject: + detection = self._find_object_in_detections(object_name, object_id) + if detection is not None: + return detection + selector = f"id '{object_id}'" if object_id else f"name '{object_name}'" + raise _PickPipelineError( + "OBJECT_NOT_DETECTED", + f"No unique current detection matches {selector}; scan again and use an object ID", + ) + + def _provider_candidates( + self, detection: DetObject, transaction: _PickTransaction + ) -> list[GraspCandidate]: + if self._grasp_generator is None: + if not self.config.heuristic_grasp_fallback: + raise _PickPipelineError( + "GRASP_PROVIDER_UNAVAILABLE", + "No grasp proposal provider is connected and heuristic fallback is disabled", + ) + transaction.proposal_source = "heuristic" + poses = self._generate_grasps_for_pick(detection.name, detection.object_id) + if not poses: + raise _PickPipelineError( + "GRASP_GENERATION_FAILED", + f"Heuristic grasp generation failed for '{detection.name}'", + ) + return [GraspCandidate(pose=pose, score=0.0) for pose in poses] + + if self._object_scene is None: + raise _PickPipelineError( + "GRASP_PROVIDER_UNAVAILABLE", + "No object-scene provider is connected for learned grasp input", + ) + + pointcloud = self._object_scene.get_object_pointcloud_by_object_id(detection.object_id) + if pointcloud is None: + raise _PickPipelineError( + "GRASP_INPUT_INVALID", + f"No point cloud is available for object '{detection.object_id}'", + ) + points = pointcloud.points_f32() + if points.ndim != 2 or points.shape[1] != 3 or len(points) == 0: + raise _PickPipelineError( + "GRASP_INPUT_INVALID", + f"Object '{detection.object_id}' has an empty or invalid point cloud", + ) + if ( + pointcloud.ts is None + or time.time() - pointcloud.ts > self.config.max_object_pointcloud_age + ): + raise _PickPipelineError( + "GRASP_INPUT_INVALID", + f"Object '{detection.object_id}' point cloud is stale", + ) + if pointcloud.frame_id != self.config.planning_frame: + raise _PickPipelineError( + "GRASP_FRAME_MISMATCH", + f"Object cloud frame '{pointcloud.frame_id}' does not match " + f"planning frame '{self.config.planning_frame}'", + ) + + try: + proposals = self._grasp_generator.propose_grasps(pointcloud) + except Exception as exc: + raise _PickPipelineError( + "GRASP_GENERATION_FAILED", f"Grasp proposal failed: {exc}" + ) from exc + if proposals.header.frame_id != self.config.planning_frame: + raise _PickPipelineError( + "GRASP_FRAME_MISMATCH", + f"Proposal frame '{proposals.header.frame_id}' does not match " + f"planning frame '{self.config.planning_frame}'", + ) + if not proposals.candidates: + raise _PickPipelineError( + "GRASP_GENERATION_FAILED", + f"No grasp proposals were generated for '{detection.name}'", + ) + return sorted(proposals.candidates, key=lambda candidate: candidate.score, reverse=True) + + @staticmethod + def _valid_candidate(candidate: GraspCandidate) -> bool: + pose = candidate.pose + values = np.asarray( + [ + pose.position.x, + pose.position.y, + pose.position.z, + pose.orientation.x, + pose.orientation.y, + pose.orientation.z, + pose.orientation.w, + candidate.score, + ], + dtype=float, + ) + quaternion = values[3:7] + return bool( + np.all(np.isfinite(values)) and np.isclose(np.linalg.norm(quaternion), 1.0, atol=1e-5) + ) + + def _select_feasible_grasp( + self, + candidates: list[GraspCandidate], + robot_name: str, + robot_pre_grasp_offset: float, + transaction: _PickTransaction, + ) -> _FeasibleGrasp: + vector = Vector3(self.config.grasp_approach_vector) + pre_offset = self.config.grasp_pre_grasp_offset or robot_pre_grasp_offset + retreat_offset = self.config.grasp_retreat_offset or pre_offset + limit = min(len(candidates), self.config.max_grasp_candidates_to_check) + + for rank, candidate in enumerate(candidates[:limit], start=1): + if not self._valid_candidate(candidate): + transaction.rejections[_CandidateRejection.INVALID.value] += 1 + continue + pre_grasp = self._compute_pre_grasp_pose(candidate.pose, pre_offset, vector) + retreat = self._compute_pre_grasp_pose(candidate.pose, retreat_offset, vector) + targets = ( + (_CandidateRejection.PRE_GRASP_INFEASIBLE, pre_grasp), + (_CandidateRejection.GRASP_INFEASIBLE, candidate.pose), + (_CandidateRejection.RETREAT_INFEASIBLE, retreat), + ) + feasible = True + for rejection, target in targets: + if not self.inverse_kinematics_single( + target, robot_name=robot_name, check_collision=True + ).is_success(): + transaction.rejections[rejection.value] += 1 + feasible = False + break + if feasible: + return _FeasibleGrasp(candidate, rank, pre_grasp, retreat) + + summary = ", ".join( + f"{reason}={count}" for reason, count in sorted(transaction.rejections.items()) + ) + raise _PickPipelineError( + "GRASP_ATTEMPTS_EXHAUSTED", + f"No feasible grasp among {limit} candidate(s)" + (f" ({summary})" if summary else ""), + ) + + def _verify_grasp(self, robot_name: str) -> _GraspVerification: + verification = self.config.grasp_verification + if not verification.enabled: + return _GraspVerification(True, None, "gripper feedback verification disabled") + + deadline = time.monotonic() + verification.timeout + last_position: float | None = None + while time.monotonic() < deadline: + last_position = self.get_gripper(robot_name) + if last_position is not None: + closes_upward = verification.closed_position > verification.open_position + empty = ( + last_position >= verification.held_threshold + if closes_upward + else last_position <= verification.held_threshold + ) + if empty: + return _GraspVerification( + False, last_position, "gripper reached the empty-closed region" + ) + time.sleep(verification.poll_interval) + + if last_position is None: + return _GraspVerification(False, None, "gripper feedback was unavailable") + movement = abs(last_position - verification.open_position) + if movement < 1e-3: + return _GraspVerification( + False, last_position, "gripper did not leave the open position" + ) + closes_upward = verification.closed_position > verification.open_position + held = ( + last_position < verification.held_threshold + if closes_upward + else last_position > verification.held_threshold + ) + detail = ( + "grasp verified by gripper closure feedback" + if held + else "gripper reached the empty-closed region" + ) + return _GraspVerification(held, last_position, detail) + + @staticmethod + def _phase_failure( + transaction: _PickTransaction, + code: ManipulationSkillError, + message: str, + ) -> SkillResult[ManipulationSkillError]: + may_hold = transaction.gripper_closed + suffix = "; object may be held" if may_hold else "" + result = SkillResult[ManipulationSkillError].fail( + code, f"{transaction.phase.value}: {message}{suffix}" + ) + result.metadata = { + "phase": transaction.phase.value, + "object_id": transaction.object_id, + "proposal_source": transaction.proposal_source, + "object_may_be_held": may_hold, + "rejections": dict(transaction.rejections), + } + if transaction.selected is not None: + result.metadata.update( + candidate_rank=transaction.selected.rank, + candidate_score=transaction.selected.candidate.score, + ) + return result + + def _execute_selected_pick( + self, transaction: _PickTransaction, robot_name: str + ) -> SkillResult[ManipulationSkillError]: + assert transaction.selected is not None + selected = transaction.selected + verification = self.config.grasp_verification + + transaction.phase = _PickPhase.PREPARE + lift = self._lift_if_low(robot_name) + if not lift.is_success(): + return self._phase_failure( + transaction, lift.error_code or "EXECUTION_FAILED", lift.message + ) + if not self._set_gripper_position(float(verification.open_position), robot_name): + return self._phase_failure(transaction, "GRIPPER_FAILED", "open command failed") + + transaction.phase = _PickPhase.APPROACH + if not self.plan_to_pose(selected.pre_grasp_pose, robot_name): + return self._phase_failure(transaction, "PLANNING_FAILED", "pre-grasp planning failed") + execution = self._preview_execute_wait(robot_name) + if not execution.is_success(): + return self._phase_failure( + transaction, execution.error_code or "EXECUTION_FAILED", execution.message + ) + + transaction.phase = _PickPhase.GRASP + if not self.plan_to_pose(selected.candidate.pose, robot_name): + return self._phase_failure(transaction, "PLANNING_FAILED", "grasp planning failed") + execution = self._preview_execute_wait(robot_name) + if not execution.is_success(): + return self._phase_failure( + transaction, execution.error_code or "EXECUTION_FAILED", execution.message + ) + + transaction.phase = _PickPhase.CLOSE + if not self._set_gripper_position(float(verification.closed_position), robot_name): + return self._phase_failure(transaction, "GRIPPER_FAILED", "close command failed") + transaction.gripper_closed = True + + transaction.phase = _PickPhase.VERIFY + verified = self._verify_grasp(robot_name) + if not verified.held: + return self._phase_failure(transaction, "GRASP_VERIFICATION_FAILED", verified.detail) + + transaction.phase = _PickPhase.RETREAT + if not self.plan_to_pose(selected.retreat_pose, robot_name): + return self._phase_failure(transaction, "PLANNING_FAILED", "retreat planning failed") + execution = self._preview_execute_wait(robot_name) + if not execution.is_success(): + return self._phase_failure( + transaction, execution.error_code or "EXECUTION_FAILED", execution.message + ) + + transaction.phase = _PickPhase.DONE + self._last_pick_pose = selected.candidate.pose + return SkillResult.ok( + f"Pick complete — grasped '{transaction.object_name}' using candidate " + f"{selected.rank} (score={selected.candidate.score:.4f}); {verified.detail}", + object_id=transaction.object_id, + proposal_source=transaction.proposal_source, + candidate_rank=selected.rank, + candidate_score=selected.candidate.score, + verification=verified.detail, + rejections=dict(transaction.rejections), + ) + @skill def pick( self, @@ -479,80 +882,48 @@ def pick( object_id: Optional unique object ID from perception for precise identification. robot_name: Robot to use (only needed for multi-arm setups). """ - robot = self._get_robot(robot_name) - if robot is None: - return SkillResult.fail("ROBOT_NOT_FOUND", "Robot not found") - rname, _, config, _ = robot - pre_grasp_offset = config.pre_grasp_offset - - # 1. Generate grasps (uses already-cached detections — call scan_objects first) - logger.info(f"Generating grasp poses for '{object_name}'...") - grasp_poses = self._generate_grasps_for_pick(object_name, object_id) - if not grasp_poses: - return SkillResult.fail( - "GRASP_GENERATION_FAILED", - f"No grasp poses found for '{object_name}'. Object may not be detected.", - ) + if not self._pick_guard.acquire(blocking=False): + return SkillResult.fail("PICK_BUSY", "Another pick transaction is active") - # Lift if EE is low before approaching - lift = self._lift_if_low(rname) - if not lift.is_success(): - return lift - - # 2. Try each grasp candidate - max_attempts = min(len(grasp_poses), 5) - for i, grasp_pose in enumerate(grasp_poses[:max_attempts]): - # Reduce pre-grasp height for far objects (arm can't reach high + far) - gp = grasp_pose.position - xy_dist = (gp.x**2 + gp.y**2) ** 0.5 - offset = pre_grasp_offset if xy_dist < _FAR_REACH_XY_THRESHOLD else 0.05 - pre_grasp_pose = self._compute_pre_grasp_pose(grasp_pose, offset) - - logger.info(f"Planning approach to pre-grasp (attempt {i + 1}/{max_attempts})...") - if not self.plan_to_pose(pre_grasp_pose, rname): - logger.info(f"Grasp candidate {i + 1} approach planning failed, trying next") - continue # Try next candidate - - # 3. Open gripper before approach - logger.info("Opening gripper...") - self._set_gripper_position(0.85, rname) - time.sleep(0.5) - - # 4. Execute approach to pre-grasp - exec_result = self._preview_execute_wait(rname) - if not exec_result.is_success(): - return exec_result - - # 5. Move to grasp pose - logger.info("Moving to grasp position...") - if not self.plan_to_pose(grasp_pose, rname): - return SkillResult.fail("PLANNING_FAILED", "Grasp pose planning failed") - exec_result = self._preview_execute_wait(rname) - if not exec_result.is_success(): - return exec_result - - # 6. Close gripper - logger.info("Closing gripper...") - self._set_gripper_position(0.0, rname) - time.sleep(1.5) # Wait for gripper to close - - # 7. Retract to pre-grasp - logger.info("Retracting with object...") - if not self.plan_to_pose(pre_grasp_pose, rname): - return SkillResult.fail("PLANNING_FAILED", "Retract planning failed") - exec_result = self._preview_execute_wait(rname) - if not exec_result.is_success(): - return exec_result - - # Store pick pose so place_back() can return with same orientation - self._last_pick_pose = grasp_pose - - return SkillResult.ok(f"Pick complete — grasped '{object_name}' successfully") - - return SkillResult.fail( - "GRASP_ATTEMPTS_EXHAUSTED", - f"All {max_attempts} grasp attempts failed for '{object_name}'", - ) + transaction = _PickTransaction() + suppression = None + result: SkillResult[ManipulationSkillError] + try: + robot = self._get_robot(robot_name) + if robot is None: + return SkillResult.fail("ROBOT_NOT_FOUND", "Robot not found") + rname, _, robot_config, _ = robot + + detection = self._require_pick_object(object_name, object_id) + transaction.object_id = detection.object_id + transaction.object_name = detection.name + transaction.phase = _PickPhase.PROPOSE + candidates = self._provider_candidates(detection, transaction) + + if self._world_monitor is None: + raise _PickPipelineError( + "WORLD_MONITOR_UNAVAILABLE", "Planning world monitor is unavailable" + ) + + with self._world_monitor.suppress_object_obstacle(detection.object_id) as suppression: + transaction.phase = _PickPhase.SELECT + transaction.selected = self._select_feasible_grasp( + candidates, rname, robot_config.pre_grasp_offset, transaction + ) + result = self._execute_selected_pick(transaction, rname) + if suppression.cleanup_error is not None: + if result.is_success(): + return self._phase_failure( + transaction, "WORLD_MONITOR_UNAVAILABLE", suppression.cleanup_error + ) + result.message = f"{result.message}; cleanup: {suppression.cleanup_error}" + return result + except _PickPipelineError as exc: + return self._phase_failure(transaction, exc.code, str(exc)) + except RuntimeError as exc: + return self._phase_failure(transaction, "WORLD_MONITOR_UNAVAILABLE", str(exc)) + finally: + self._pick_guard.release() @skill def place( diff --git a/dimos/manipulation/planning/monitor/test_world_obstacle_suppression.py b/dimos/manipulation/planning/monitor/test_world_obstacle_suppression.py new file mode 100644 index 0000000000..995a137931 --- /dev/null +++ b/dimos/manipulation/planning/monitor/test_world_obstacle_suppression.py @@ -0,0 +1,178 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import threading +import time +from types import SimpleNamespace + +import open3d as o3d +import pytest +from pytest_mock import MockerFixture + +from dimos.manipulation.planning.monitor.world_obstacle_monitor import WorldObstacleMonitor +from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped +from dimos.msgs.geometry_msgs.Vector3 import Vector3 +from dimos.msgs.sensor_msgs.Image import Image +from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 +from dimos.perception.experimental.object import Object + + +def _object(object_id: str) -> Object: + return Object( + name=f"object-{object_id}", + object_id=object_id, + center=Vector3(0.4, 0.0, 0.2), + size=Vector3(0.05, 0.05, 0.1), + pose=PoseStamped(), + pointcloud=PointCloud2(o3d.geometry.PointCloud()), + bbox=(0.0, 0.0, 1.0, 1.0), + track_id=0, + class_id=0, + confidence=1.0, + ts=time.time(), + image=Image(), + ) + + +def _monitor(mocker: MockerFixture) -> tuple[WorldObstacleMonitor, SimpleNamespace]: + parent = SimpleNamespace( + _lock=threading.RLock(), + add_obstacle=mocker.Mock( + side_effect=lambda obstacle: f"world-{obstacle.name}-{time.monotonic_ns()}" + ), + remove_obstacle=mocker.Mock(return_value=True), + ) + monitor = WorldObstacleMonitor(parent) # type: ignore[arg-type] + monitor.start() + return monitor, parent + + +def test_suppression_skips_target_but_refreshes_other_objects( + mocker: MockerFixture, +) -> None: + monitor, parent = _monitor(mocker) + target = _object("target") + other = _object("other") + monitor.on_objects([target, other]) + monitor.refresh_obstacles() + parent.add_obstacle.reset_mock() + + with monitor.suppress_object_obstacle("target") as suppression: + refreshed = monitor.refresh_obstacles() + + assert suppression.removed is True + assert [item["object_id"] for item in refreshed] == ["other"] + assert set(monitor._object_obstacles) == {"other"} + + assert set(monitor._object_obstacles) == {"target", "other"} + assert parent.remove_obstacle.call_count >= 1 + + +def test_suppression_wins_race_with_in_progress_refresh(mocker: MockerFixture) -> None: + monitor, _ = _monitor(mocker) + monitor.on_objects([_object("target"), _object("other")]) + conversion_started = threading.Event() + continue_conversion = threading.Event() + original_conversion = monitor._object_to_obstacle + + def delayed_conversion(obj: Object): + if obj.object_id == "target": + conversion_started.set() + assert continue_conversion.wait(timeout=1.0) + return original_conversion(obj) + + mocker.patch.object(monitor, "_object_to_obstacle", side_effect=delayed_conversion) + refreshed: list[list[dict[str, object]]] = [] + thread = threading.Thread(target=lambda: refreshed.append(monitor.refresh_obstacles())) + thread.start() + assert conversion_started.wait(timeout=1.0) + + with monitor.suppress_object_obstacle("target"): + continue_conversion.set() + thread.join(timeout=1.0) + + assert not thread.is_alive() + assert [item["object_id"] for item in refreshed[0]] == ["other"] + assert set(monitor._object_obstacles) == {"other"} + + assert set(monitor._object_obstacles) == {"target", "other"} + + +def test_nested_suppression_removes_and_restores_once(mocker: MockerFixture) -> None: + monitor, parent = _monitor(mocker) + monitor.on_objects([_object("target")]) + monitor.refresh_obstacles() + parent.add_obstacle.reset_mock() + parent.remove_obstacle.reset_mock() + + with monitor.suppress_object_obstacle("target"): + with monitor.suppress_object_obstacle("target"): + assert "target" not in monitor._object_obstacles + assert "target" not in monitor._object_obstacles + + assert parent.remove_obstacle.call_count == 1 + assert parent.add_obstacle.call_count == 1 + assert "target" in monitor._object_obstacles + + +def test_suppression_restores_after_cancellation(mocker: MockerFixture) -> None: + class Cancelled(BaseException): + pass + + monitor, _ = _monitor(mocker) + monitor.on_objects([_object("target")]) + monitor.refresh_obstacles() + + with pytest.raises(Cancelled): + with monitor.suppress_object_obstacle("target"): + raise Cancelled + + assert monitor._object_suppressions == {} + assert "target" in monitor._object_obstacles + + +def test_suppression_reports_restore_failure_without_masking_body( + mocker: MockerFixture, +) -> None: + monitor, parent = _monitor(mocker) + monitor.on_objects([_object("target")]) + monitor.refresh_obstacles() + parent.add_obstacle.side_effect = None + parent.add_obstacle.return_value = "" + + with monitor.suppress_object_obstacle("target") as suppression: + body_completed = True + + assert body_completed is True + assert suppression.cleanup_error == "failed to restore obstacle for object 'target'" + assert "target" not in monitor._object_obstacles + + +def test_failed_suppression_removal_restores_internal_tracking( + mocker: MockerFixture, +) -> None: + monitor, parent = _monitor(mocker) + monitor.on_objects([_object("target")]) + monitor.refresh_obstacles() + parent.remove_obstacle.return_value = False + + with pytest.raises(RuntimeError, match="failed to suppress") as exc_info: + with monitor.suppress_object_obstacle("target"): + raise AssertionError("suppression body must not run") + + assert str(exc_info.value) == "failed to suppress obstacle for object 'target'" + assert monitor._object_suppressions == {} + assert "target" in monitor._object_obstacles diff --git a/dimos/manipulation/planning/monitor/world_monitor.py b/dimos/manipulation/planning/monitor/world_monitor.py index bcbb2b7ed8..dc1c616230 100644 --- a/dimos/manipulation/planning/monitor/world_monitor.py +++ b/dimos/manipulation/planning/monitor/world_monitor.py @@ -16,7 +16,7 @@ from __future__ import annotations -from collections.abc import Sequence +from collections.abc import Iterator, Sequence from contextlib import contextmanager import threading from typing import TYPE_CHECKING, Any @@ -29,7 +29,10 @@ from dimos.manipulation.planning.groups.registry import PlanningGroupRegistry from dimos.manipulation.planning.groups.utils import filter_joint_state_to_selected_joints from dimos.manipulation.planning.monitor.robot_state_monitor import RobotStateMonitor -from dimos.manipulation.planning.monitor.world_obstacle_monitor import WorldObstacleMonitor +from dimos.manipulation.planning.monitor.world_obstacle_monitor import ( + ObjectObstacleSuppression, + WorldObstacleMonitor, +) from dimos.manipulation.planning.spec.models import ( PlanningSceneInfo, VisualizationSession, @@ -313,6 +316,15 @@ def remove_object_obstacle(self, object_id: str) -> bool: return self._obstacle_monitor.remove_object_obstacle(object_id) return False + @contextmanager + def suppress_object_obstacle(self, object_id: str) -> Iterator[ObjectObstacleSuppression]: + """Temporarily exclude one perception object from collision checking.""" + if self._obstacle_monitor is None: + yield ObjectObstacleSuppression(object_id=object_id) + return + with self._obstacle_monitor.suppress_object_obstacle(object_id) as suppression: + yield suppression + def clear_perception_obstacles(self) -> int: """Remove all perception obstacles. Returns count removed.""" if self._obstacle_monitor is not None: diff --git a/dimos/manipulation/planning/monitor/world_obstacle_monitor.py b/dimos/manipulation/planning/monitor/world_obstacle_monitor.py index 481e6976ca..6f870b171c 100644 --- a/dimos/manipulation/planning/monitor/world_obstacle_monitor.py +++ b/dimos/manipulation/planning/monitor/world_obstacle_monitor.py @@ -26,7 +26,10 @@ from __future__ import annotations -from dataclasses import replace +from collections import Counter +from collections.abc import Iterator +from contextlib import contextmanager +from dataclasses import dataclass, replace import time from typing import TYPE_CHECKING, Any @@ -49,6 +52,15 @@ logger = setup_logger() +@dataclass +class ObjectObstacleSuppression: + """Result of a scoped object-obstacle suppression.""" + + object_id: str + removed: bool = False + cleanup_error: str | None = None + + class WorldObstacleMonitor: """Monitors world obstacles and updates its parent WorldMonitor. @@ -95,6 +107,7 @@ def __init__( self._object_cache: dict[str, tuple[Object, float, float]] = {} # object_id -> obstacle_id (objects currently added to Drake world) self._object_obstacles: dict[str, str] = {} + self._object_suppressions: Counter[str] = Counter() # Running state self._running = False @@ -123,6 +136,7 @@ def clear_tracking(self) -> None: self._perception_objects.clear() self._perception_timestamps.clear() self._object_obstacles.clear() + self._object_suppressions.clear() def on_collision_object(self, msg: CollisionObjectMessage) -> None: """Handle explicit collision object message. @@ -497,6 +511,8 @@ def refresh_obstacles(self, min_duration: float = 0.0) -> list[dict[str, Any]]: for oid, (obj, first_seen, last_seen) in self._object_cache.items(): if not isinstance(obj, Object): continue + if self._object_suppressions[oid] > 0: + continue if last_seen - first_seen < min_duration: continue eligible.append((oid, obj)) @@ -517,6 +533,10 @@ def refresh_obstacles(self, min_duration: float = 0.0) -> list[dict[str, Any]]: result: list[dict[str, Any]] = [] for oid, obj, obstacle in prepared: + # Suppression may have started while obstacle geometry was + # computed outside the lock. + if self._object_suppressions[oid] > 0: + continue assert isinstance(obj, Object) obs_id = self._parent.add_obstacle(obstacle) if not obs_id: @@ -552,6 +572,58 @@ def remove_object_obstacle(self, object_id: str) -> bool: logger.info(f"Removed obstacle for object '{object_id}'") return True + @contextmanager + def suppress_object_obstacle(self, object_id: str) -> Iterator[ObjectObstacleSuppression]: + """Exclude one cached object obstacle for the lifetime of the context. + + Nested callers share one removal. Live refreshes skip suppressed object + IDs, and the outermost exit restores the latest cached geometry. + """ + handle = ObjectObstacleSuppression(object_id=object_id) + with self._lock: + depth = self._object_suppressions[object_id] + self._object_suppressions[object_id] = depth + 1 + if depth == 0: + obstacle_id = self._object_obstacles.get(object_id) + if obstacle_id is not None: + if not self._parent.remove_obstacle(obstacle_id): + del self._object_suppressions[object_id] + raise RuntimeError(f"failed to suppress obstacle for object '{object_id}'") + del self._object_obstacles[object_id] + handle.removed = True + try: + yield handle + finally: + self._release_object_suppression(handle) + + def _release_object_suppression(self, handle: ObjectObstacleSuppression) -> None: + object_id = handle.object_id + cached: Object | None = None + with self._lock: + depth = self._object_suppressions.get(object_id, 0) + if depth > 1: + self._object_suppressions[object_id] = depth - 1 + return + self._object_suppressions.pop(object_id, None) + entry = self._object_cache.get(object_id) + if entry is not None: + cached = entry[0] + + if cached is None: + return + obstacle = self._object_to_obstacle(cached) + with self._lock: + if self._object_suppressions.get(object_id, 0) > 0: + return + if object_id in self._object_obstacles: + return + obstacle_id = self._parent.add_obstacle(obstacle) + if obstacle_id: + self._object_obstacles[object_id] = obstacle_id + return + handle.cleanup_error = f"failed to restore obstacle for object '{object_id}'" + logger.error(handle.cleanup_error) + def clear_perception_obstacles(self) -> int: """Remove all object obstacles from the planning world. diff --git a/dimos/manipulation/skill_errors.py b/dimos/manipulation/skill_errors.py index 9a17085ec5..c980149c78 100644 --- a/dimos/manipulation/skill_errors.py +++ b/dimos/manipulation/skill_errors.py @@ -35,6 +35,11 @@ "COLLISION_AT_START", "GRASP_GENERATION_FAILED", "GRASP_ATTEMPTS_EXHAUSTED", + "GRASP_PROVIDER_UNAVAILABLE", + "GRASP_INPUT_INVALID", + "GRASP_FRAME_MISMATCH", + "GRASP_VERIFICATION_FAILED", + "PICK_BUSY", "GRIPPER_FAILED", "WORLD_MONITOR_UNAVAILABLE", ] diff --git a/dimos/manipulation/test_pick_and_place_unit.py b/dimos/manipulation/test_pick_and_place_unit.py index 6ee984f0e2..d15a9598fa 100644 --- a/dimos/manipulation/test_pick_and_place_unit.py +++ b/dimos/manipulation/test_pick_and_place_unit.py @@ -16,18 +16,43 @@ from __future__ import annotations +from collections import Counter +from contextlib import nullcontext +import json +from types import SimpleNamespace from unittest.mock import patch +import numpy as np import open3d as o3d import pytest +from pytest_mock import MockerFixture +from dimos.agents.skill_result import SkillResult +from dimos.core.coordination.blueprints import BlueprintAtom, autoconnect +from dimos.core.coordination.module_coordinator import _resolve_single_ref from dimos.core.module import ModuleBase -from dimos.manipulation.pick_and_place_module import PickAndPlaceModule +from dimos.manipulation.grasping.grasp_gen_x import GraspGenXModule +from dimos.manipulation.pick_and_place_module import ( + GraspVerificationConfig, + PickAndPlaceModule, + PickAndPlaceModuleConfig, + _FeasibleGrasp, + _GraspVerification, +) +from dimos.manipulation.skill_errors import ManipulationSkillError +from dimos.msgs.geometry_msgs.Pose import Pose from dimos.msgs.geometry_msgs.PoseStamped import PoseStamped +from dimos.msgs.geometry_msgs.Quaternion import Quaternion from dimos.msgs.geometry_msgs.Vector3 import Vector3 +from dimos.msgs.manipulation_msgs.GraspCandidate import GraspCandidate +from dimos.msgs.manipulation_msgs.GraspCandidateArray import GraspCandidateArray from dimos.msgs.sensor_msgs.Image import Image from dimos.msgs.sensor_msgs.PointCloud2 import PointCloud2 +from dimos.msgs.std_msgs.Header import Header from dimos.perception.experimental.object import Object as DetObject +from dimos.perception.experimental.object_scene_registration import ( + ObjectSceneRegistrationModule, +) def _make_det_object( @@ -57,7 +82,9 @@ def _make_det_object( def module() -> PickAndPlaceModule: """Create a PickAndPlaceModule with heavy base init (RPC, config) patched out.""" with patch.object(ModuleBase, "__init__", lambda self, config_args: None): - return PickAndPlaceModule() + result = PickAndPlaceModule() + result.config = PickAndPlaceModuleConfig() + return result class TestFindObjectInDetections: @@ -99,6 +126,14 @@ def test_find_missing_returns_none(self, module): result = module._find_object_in_detections("keyboard") assert result is None + def test_find_by_name_requires_unique_match(self, module): + module._detection_snapshot = [ + _make_det_object(name="cup", object_id="first"), + _make_det_object(name="red cup", object_id="second"), + ] + + assert module._find_object_in_detections("cup") is None + def test_empty_snapshot_returns_none(self, module): module._detection_snapshot = [] @@ -158,3 +193,549 @@ def test_place_back_no_pick_pose_errors(self, module): assert not result.is_success() assert result.error_code == "NO_PRIOR_POSE" assert "pick" in result.message.lower() + + +def test_grasp_pipeline_error_agent_encoding_is_structured() -> None: + result = SkillResult[ManipulationSkillError].fail("PICK_BUSY", "pick in progress") + + payload = json.loads(result.agent_encode()[0]["text"]) + + assert payload == { + "success": False, + "message": "pick in progress", + "error_code": "PICK_BUSY", + "duration_ms": 0.0, + } + + +@pytest.mark.parametrize( + ("kwargs", "message"), + [ + ({"planning_frame": " "}, "planning_frame"), + ({"grasp_approach_vector": (0.0, 0.0, 2.0)}, "unit vector"), + ( + { + "grasp_verification": { + "open_position": 0.85, + "closed_position": 0.0, + "held_threshold": 0.9, + } + }, + "held_threshold", + ), + ], +) +def test_grasp_pipeline_config_rejects_invalid_values( + kwargs: dict[str, object], message: str +) -> None: + with pytest.raises(ValueError, match=message): + PickAndPlaceModuleConfig(**kwargs) + + +def test_pick_module_declares_optional_perception_and_grasp_specs() -> None: + atom = BlueprintAtom.create(PickAndPlaceModule, kwargs={}) + + refs = {ref.name: ref for ref in atom.module_refs} + + assert refs["_object_scene"].optional is True + assert refs["_grasp_generator"].optional is True + + +@pytest.mark.parametrize( + ("ref_name", "provider"), + [ + ("_grasp_generator", GraspGenXModule), + ("_object_scene", ObjectSceneRegistrationModule), + ], +) +def test_optional_provider_resolves_when_absent_present_or_ambiguous( + ref_name: str, provider: type[ModuleBase] +) -> None: + consumer = BlueprintAtom.create(PickAndPlaceModule, kwargs={}) + module_ref = next(ref for ref in consumer.module_refs if ref.name == ref_name) + + absent = autoconnect(PickAndPlaceModule.blueprint()) + assert _resolve_single_ref(consumer, module_ref, module_ref.spec, absent, set()) is None + + present = autoconnect(PickAndPlaceModule.blueprint(), provider.blueprint()) + assert ( + _resolve_single_ref(consumer, module_ref, module_ref.spec, present, set()) == provider.name + ) + + ambiguous = autoconnect( + PickAndPlaceModule.blueprint(), + provider.blueprint(instance_name="provider-a"), + provider.blueprint(instance_name="provider-b"), + ) + with pytest.raises(Exception, match="Multiple modules met that spec"): + _resolve_single_ref(consumer, module_ref, module_ref.spec, ambiguous, set()) + + +def _pointcloud(frame_id: str = "world", timestamp: float | None = None) -> PointCloud2: + return PointCloud2.from_numpy( + np.asarray([[0.4, 0.0, 0.2], [0.41, 0.01, 0.2]], dtype=np.float32), + frame_id=frame_id, + timestamp=timestamp, + ) + + +def _candidate(x: float, score: float) -> GraspCandidate: + return GraspCandidate( + Pose(Vector3(x, 0.0, 0.2), Quaternion(0.0, 0.0, 0.0, 1.0)), + score, + ) + + +class TestProposalSelection: + def test_provider_receives_real_world_frame_cloud( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + now = 100.0 + cloud = _pointcloud(timestamp=now) + detection = _make_det_object() + scene = mocker.Mock() + scene.get_object_pointcloud_by_object_id.return_value = cloud + generator = mocker.Mock() + generator.propose_grasps.return_value = GraspCandidateArray( + Header(now, "world"), [_candidate(0.4, 0.8)] + ) + module._object_scene = scene + module._grasp_generator = generator + mocker.patch("dimos.manipulation.pick_and_place_module.time.time", return_value=now + 0.1) + + candidates = module._provider_candidates( + detection, SimpleNamespace(proposal_source="grasp_provider") + ) + + generator.propose_grasps.assert_called_once_with(cloud) + assert [(candidate.pose.position.x, candidate.score) for candidate in candidates] == [ + (0.4, 0.8) + ] + + @pytest.mark.parametrize("cloud_available", [False, True]) + def test_provider_rejects_missing_or_stale_cloud( + self, + module: PickAndPlaceModule, + mocker: MockerFixture, + cloud_available: bool, + ) -> None: + scene = mocker.Mock() + scene.get_object_pointcloud_by_object_id.return_value = ( + _pointcloud(timestamp=1.0) if cloud_available else None + ) + module._object_scene = scene + module._grasp_generator = mocker.Mock() + mocker.patch("dimos.manipulation.pick_and_place_module.time.time", return_value=100.0) + + with pytest.raises(RuntimeError, match="point cloud"): + module._provider_candidates( + _make_det_object(), SimpleNamespace(proposal_source="grasp_provider") + ) + + @pytest.mark.parametrize( + ("cloud_frame", "proposal_frame"), + [("camera", "world"), ("world", "camera")], + ) + def test_provider_rejects_frame_mismatch( + self, + module: PickAndPlaceModule, + mocker: MockerFixture, + cloud_frame: str, + proposal_frame: str, + ) -> None: + now = 100.0 + scene = mocker.Mock() + scene.get_object_pointcloud_by_object_id.return_value = _pointcloud( + cloud_frame, timestamp=now + ) + generator = mocker.Mock() + generator.propose_grasps.return_value = GraspCandidateArray( + Header(now, proposal_frame), [_candidate(0.4, 0.8)] + ) + module._object_scene = scene + module._grasp_generator = generator + mocker.patch("dimos.manipulation.pick_and_place_module.time.time", return_value=now) + + with pytest.raises(RuntimeError, match="frame"): + module._provider_candidates( + _make_det_object(), SimpleNamespace(proposal_source="grasp_provider") + ) + + def test_provider_preserves_stable_order_for_equal_scores( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + now = 100.0 + scene = mocker.Mock() + scene.get_object_pointcloud_by_object_id.return_value = _pointcloud(timestamp=now) + generator = mocker.Mock() + generator.propose_grasps.return_value = GraspCandidateArray( + Header(now, "world"), + [_candidate(0.1, 0.5), _candidate(0.2, 0.7), _candidate(0.3, 0.7)], + ) + module._object_scene = scene + module._grasp_generator = generator + mocker.patch("dimos.manipulation.pick_and_place_module.time.time", return_value=now) + + candidates = module._provider_candidates( + _make_det_object(), SimpleNamespace(proposal_source="grasp_provider") + ) + + assert [candidate.pose.position.x for candidate in candidates] == [0.2, 0.3, 0.1] + + def test_explicit_heuristic_fallback_identifies_source( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + module.config.heuristic_grasp_fallback = True + transaction = SimpleNamespace(proposal_source="grasp_provider") + pose = Pose(0.4, 0.0, 0.2) + mocker.patch.object(module, "_generate_grasps_for_pick", return_value=[pose]) + + candidates = module._provider_candidates(_make_det_object(), transaction) + + assert transaction.proposal_source == "heuristic" + assert [(candidate.pose, candidate.score) for candidate in candidates] == [(pose, 0.0)] + + def test_provider_is_required_when_fallback_is_disabled( + self, module: PickAndPlaceModule + ) -> None: + with pytest.raises(RuntimeError, match="fallback is disabled"): + module._provider_candidates( + _make_det_object(), SimpleNamespace(proposal_source="grasp_provider") + ) + + def test_selection_skips_higher_scored_infeasible_candidate( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + failed = SimpleNamespace(is_success=lambda: False) + succeeded = SimpleNamespace(is_success=lambda: True) + solve = mocker.patch.object( + module, + "inverse_kinematics_single", + side_effect=[failed, succeeded, succeeded, succeeded], + ) + transaction = SimpleNamespace(rejections=Counter()) + + selected = module._select_feasible_grasp( + [_candidate(0.4, 0.9), _candidate(0.5, 0.8)], + "arm", + 0.1, + transaction, + ) + + assert selected.rank == 2 + assert selected.candidate.score == 0.8 + assert solve.call_count == 4 + assert transaction.rejections == {"pre_grasp_infeasible": 1} + + def test_selection_rejects_malformed_candidate_and_honors_limit( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + module.config.max_grasp_candidates_to_check = 1 + invalid = _candidate(0.4, 0.9) + invalid.pose.orientation.w = 0.0 + solve = mocker.patch.object(module, "inverse_kinematics_single") + transaction = SimpleNamespace(rejections=Counter()) + + with pytest.raises(RuntimeError, match="No feasible grasp among 1"): + module._select_feasible_grasp([invalid, _candidate(0.5, 0.8)], "arm", 0.1, transaction) + + solve.assert_not_called() + assert transaction.rejections == {"invalid": 1} + + +class TestPickTransaction: + def _arrange_success( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> tuple[GraspCandidate, SimpleNamespace]: + detection = _make_det_object() + candidate = _candidate(0.4, 0.9) + selected = _FeasibleGrasp(candidate, 1, Pose(0.4, 0.0, 0.3), Pose(0.4, 0.0, 0.3)) + robot_config = SimpleNamespace(pre_grasp_offset=0.1) + mocker.patch.object( + module, "_get_robot", return_value=("arm", "robot-id", robot_config, None) + ) + mocker.patch.object(module, "_require_pick_object", return_value=detection) + mocker.patch.object(module, "_provider_candidates", return_value=[candidate]) + mocker.patch.object(module, "_select_feasible_grasp", return_value=selected) + mocker.patch.object(module, "_lift_if_low", return_value=SkillResult.ok()) + mocker.patch.object(module, "plan_to_pose", return_value=True) + mocker.patch.object(module, "_preview_execute_wait", return_value=SkillResult.ok()) + mocker.patch.object(module, "_set_gripper_position", return_value=True) + mocker.patch.object( + module, + "_verify_grasp", + return_value=_GraspVerification(True, 0.1, "verified"), + ) + suppression = SimpleNamespace(cleanup_error=None) + world = mocker.Mock() + world.suppress_object_obstacle.return_value = nullcontext(suppression) + module._world_monitor = world + return candidate, suppression + + def test_success_executes_ordered_pick_and_records_metadata( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + candidate, _ = self._arrange_success(module, mocker) + + result = module.pick("cup", object_id="abc12345") + + assert result.is_success() + assert result.metadata["candidate_rank"] == 1 + assert result.metadata["candidate_score"] == 0.9 + assert module._last_pick_pose is candidate.pose + assert module._set_gripper_position.call_args_list == [ + mocker.call(0.85, "arm"), + mocker.call(0.0, "arm"), + ] + assert module.plan_to_pose.call_count == 3 + + def test_retreat_failure_keeps_gripper_closed( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + self._arrange_success(module, mocker) + module.plan_to_pose.side_effect = [True, True, False] + + result = module.pick("cup", object_id="abc12345") + + assert result.error_code == "PLANNING_FAILED" + assert result.metadata["object_may_be_held"] is True + assert module._set_gripper_position.call_args_list == [ + mocker.call(0.85, "arm"), + mocker.call(0.0, "arm"), + ] + + def test_concurrent_pick_is_rejected_without_robot_access( + self, + module: PickAndPlaceModule, + mocker: MockerFixture, + ) -> None: + get_robot = mocker.patch.object(module, "_get_robot") + log = mocker.patch("dimos.agents.annotation.logger.info") + module._pick_guard.acquire() + try: + result = module.pick("cup") + finally: + module._pick_guard.release() + + assert result.error_code == "PICK_BUSY" + get_robot.assert_not_called() + log.assert_called_once() + assert log.call_args.args[:3] == ( + "SKILL %s result=%s duration_ms=%.1f", + "pick", + "PICK_BUSY", + ) + + def test_cleanup_failure_does_not_hide_primary_failure( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + _, suppression = self._arrange_success(module, mocker) + suppression.cleanup_error = "restore failed" + module.plan_to_pose.side_effect = [False] + + result = module.pick("cup", object_id="abc12345") + + assert result.error_code == "PLANNING_FAILED" + assert "cleanup: restore failed" in result.message + + def test_cleanup_failure_turns_success_into_scene_failure( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + _, suppression = self._arrange_success(module, mocker) + suppression.cleanup_error = "restore failed" + + result = module.pick("cup", object_id="abc12345") + + assert result.error_code == "WORLD_MONITOR_UNAVAILABLE" + assert "restore failed" in result.message + + @pytest.mark.parametrize( + ("setup", "expected_code", "expected_phase"), + [ + ("prepare", "EXECUTION_FAILED", "PREPARE"), + ("open", "GRIPPER_FAILED", "PREPARE"), + ("approach_planning", "PLANNING_FAILED", "APPROACH"), + ("approach_execution", "EXECUTION_FAILED", "APPROACH"), + ("grasp_planning", "PLANNING_FAILED", "GRASP"), + ("grasp_execution", "EXECUTION_FAILED", "GRASP"), + ("close", "GRIPPER_FAILED", "CLOSE"), + ("verification", "GRASP_VERIFICATION_FAILED", "VERIFY"), + ("retreat_planning", "PLANNING_FAILED", "RETREAT"), + ("retreat_execution", "EXECUTION_FAILED", "RETREAT"), + ], + ) + def test_phase_failures_stop_the_pipeline( + self, + module: PickAndPlaceModule, + mocker: MockerFixture, + setup: str, + expected_code: str, + expected_phase: str, + ) -> None: + self._arrange_success(module, mocker) + if setup == "prepare": + module._lift_if_low.return_value = SkillResult.fail("EXECUTION_FAILED", "lift failed") + elif setup == "open": + module._set_gripper_position.return_value = False + elif setup == "approach_planning": + module.plan_to_pose.side_effect = [False] + elif setup == "approach_execution": + module._preview_execute_wait.side_effect = [ + SkillResult.fail("EXECUTION_FAILED", "rejected") + ] + elif setup == "grasp_planning": + module.plan_to_pose.side_effect = [True, False] + elif setup == "grasp_execution": + module._preview_execute_wait.side_effect = [ + SkillResult.ok(), + SkillResult.fail("EXECUTION_FAILED", "rejected"), + ] + elif setup == "close": + module._set_gripper_position.side_effect = [True, False] + elif setup == "verification": + module._verify_grasp.return_value = _GraspVerification(False, 0.0, "empty close") + elif setup == "retreat_planning": + module.plan_to_pose.side_effect = [True, True, False] + else: + module._preview_execute_wait.side_effect = [ + SkillResult.ok(), + SkillResult.ok(), + SkillResult.fail("EXECUTION_FAILED", "rejected"), + ] + + result = module.pick("cup", object_id="abc12345") + + assert result.error_code == expected_code + assert result.metadata["phase"] == expected_phase + + +def test_full_pick_pipeline_uses_real_messages_and_fake_boundary_providers( + module: PickAndPlaceModule, mocker: MockerFixture +) -> None: + now = 100.0 + detection = _make_det_object() + module._detection_snapshot = [detection] + scene = mocker.Mock() + scene.get_object_pointcloud_by_object_id.return_value = _pointcloud(timestamp=now) + generator = mocker.Mock() + generator.propose_grasps.return_value = GraspCandidateArray( + Header(now, "world"), + [_candidate(0.4, 0.9), _candidate(0.5, 0.8)], + ) + module._object_scene = scene + module._grasp_generator = generator + robot_config = SimpleNamespace(pre_grasp_offset=0.1) + mocker.patch.object(module, "_get_robot", return_value=("arm", "robot-id", robot_config, None)) + failed = SimpleNamespace(is_success=lambda: False) + feasible = SimpleNamespace(is_success=lambda: True) + ik = mocker.patch.object( + module, + "inverse_kinematics_single", + side_effect=[failed, feasible, feasible, feasible], + ) + mocker.patch.object(module, "_lift_if_low", return_value=SkillResult.ok()) + plan = mocker.patch.object(module, "plan_to_pose", return_value=True) + execute = mocker.patch.object(module, "_preview_execute_wait", return_value=SkillResult.ok()) + gripper = mocker.patch.object(module, "_set_gripper_position", return_value=True) + suppression = SimpleNamespace(cleanup_error=None) + world = mocker.Mock() + world.suppress_object_obstacle.return_value = nullcontext(suppression) + module._world_monitor = world + mocker.patch("dimos.manipulation.pick_and_place_module.time.time", return_value=now) + + result = module.pick("cup", object_id="abc12345") + + assert result.is_success() + assert result.metadata["proposal_source"] == "grasp_provider" + assert result.metadata["candidate_rank"] == 2 + assert result.metadata["candidate_score"] == 0.8 + assert result.metadata["rejections"] == {"pre_grasp_infeasible": 1} + scene.get_object_pointcloud_by_object_id.assert_called_once_with("abc12345") + generator.propose_grasps.assert_called_once_with(scene.get_object_pointcloud_by_object_id()) + world.suppress_object_obstacle.assert_called_once_with("abc12345") + assert ik.call_count == 4 + assert plan.call_count == 3 + assert execute.call_count == 3 + assert gripper.call_args_list == [mocker.call(0.85, "arm"), mocker.call(0.0, "arm")] + + +class TestGraspVerification: + def test_empty_close_fails_immediately( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + module.config.grasp_verification = GraspVerificationConfig( + enabled=True, + timeout=1.0, + poll_interval=0.1, + held_threshold=0.02, + ) + mocker.patch.object(module, "get_gripper", return_value=0.0) + mocker.patch( + "dimos.manipulation.pick_and_place_module.time.monotonic", + side_effect=[0.0, 0.1], + ) + + result = module._verify_grasp("arm") + + assert result == _GraspVerification(False, 0.0, "gripper reached the empty-closed region") + + def test_held_position_succeeds_after_timeout( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + module.config.grasp_verification = GraspVerificationConfig( + enabled=True, + timeout=1.0, + poll_interval=0.1, + held_threshold=0.02, + ) + mocker.patch.object(module, "get_gripper", return_value=0.1) + mocker.patch( + "dimos.manipulation.pick_and_place_module.time.monotonic", + side_effect=[0.0, 0.1, 1.1], + ) + sleep = mocker.patch("dimos.manipulation.pick_and_place_module.time.sleep") + + result = module._verify_grasp("arm") + + assert result == _GraspVerification(True, 0.1, "grasp verified by gripper closure feedback") + sleep.assert_called_once_with(0.1) + + def test_no_gripper_motion_is_not_misclassified_as_a_grasp( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + module.config.grasp_verification = GraspVerificationConfig( + enabled=True, + timeout=1.0, + poll_interval=0.1, + held_threshold=0.02, + ) + mocker.patch.object(module, "get_gripper", return_value=0.85) + mocker.patch( + "dimos.manipulation.pick_and_place_module.time.monotonic", + side_effect=[0.0, 0.1, 1.1], + ) + mocker.patch("dimos.manipulation.pick_and_place_module.time.sleep") + + result = module._verify_grasp("arm") + + assert result == _GraspVerification(False, 0.85, "gripper did not leave the open position") + + def test_feedback_timeout_is_reported( + self, module: PickAndPlaceModule, mocker: MockerFixture + ) -> None: + module.config.grasp_verification = GraspVerificationConfig( + enabled=True, + timeout=1.0, + poll_interval=0.1, + held_threshold=0.02, + ) + mocker.patch.object(module, "get_gripper", return_value=None) + mocker.patch( + "dimos.manipulation.pick_and_place_module.time.monotonic", + side_effect=[0.0, 0.1, 1.1], + ) + mocker.patch("dimos.manipulation.pick_and_place_module.time.sleep") + + result = module._verify_grasp("arm") + + assert result == _GraspVerification(False, None, "gripper feedback was unavailable") diff --git a/dimos/msgs/manipulation_msgs/GraspCandidate.py b/dimos/msgs/manipulation_msgs/GraspCandidate.py new file mode 100644 index 0000000000..f83db6016f --- /dev/null +++ b/dimos/msgs/manipulation_msgs/GraspCandidate.py @@ -0,0 +1,40 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +import math +import pickle + +from dimos.msgs.geometry_msgs.Pose import Pose + + +class GraspCandidate: + """A robot TCP pose and its generator-local ranking score.""" + + msg_name = "manipulation_msgs.GraspCandidate" + + def __init__(self, pose: Pose | None = None, score: float = 0.0) -> None: + self.pose = pose if pose is not None else Pose(0.0, 0.0, 0.0) + self.score = float(score) + if not math.isfinite(self.score): + raise ValueError("GraspCandidate.score must be finite") + + def encode(self) -> bytes: + return pickle.dumps({"pose": self.pose, "score": self.score}) + + @classmethod + def decode(cls, data: bytes) -> GraspCandidate: + value = pickle.loads(data) + return cls(value["pose"], value["score"]) diff --git a/dimos/msgs/manipulation_msgs/GraspCandidateArray.py b/dimos/msgs/manipulation_msgs/GraspCandidateArray.py new file mode 100644 index 0000000000..5144d0b5b9 --- /dev/null +++ b/dimos/msgs/manipulation_msgs/GraspCandidateArray.py @@ -0,0 +1,48 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import annotations + +from collections.abc import Iterator +import pickle + +from dimos.msgs.manipulation_msgs.GraspCandidate import GraspCandidate +from dimos.msgs.std_msgs.Header import Header + + +class GraspCandidateArray: + """Ordered grasp proposals sharing one input-cloud header.""" + + msg_name = "manipulation_msgs.GraspCandidateArray" + + def __init__( + self, header: Header | None = None, candidates: list[GraspCandidate] | None = None + ) -> None: + self.header = header if header is not None else Header(0.0) + self.candidates = candidates if candidates is not None else [] + + def __len__(self) -> int: + return len(self.candidates) + + def __iter__(self) -> Iterator[GraspCandidate]: + return iter(self.candidates) + + def encode(self) -> bytes: + """Encode using the repository's pickle transport convention.""" + return pickle.dumps({"header": self.header, "candidates": self.candidates}) + + @classmethod + def decode(cls, data: bytes) -> GraspCandidateArray: + value = pickle.loads(data) + return cls(value["header"], value["candidates"]) diff --git a/dimos/msgs/manipulation_msgs/test_grasp_candidate.py b/dimos/msgs/manipulation_msgs/test_grasp_candidate.py new file mode 100644 index 0000000000..9538beda52 --- /dev/null +++ b/dimos/msgs/manipulation_msgs/test_grasp_candidate.py @@ -0,0 +1,51 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import pytest + +from dimos.msgs.geometry_msgs.Pose import Pose +from dimos.msgs.manipulation_msgs.GraspCandidate import GraspCandidate +from dimos.msgs.manipulation_msgs.GraspCandidateArray import GraspCandidateArray +from dimos.msgs.std_msgs.Header import Header + + +def test_grasp_candidate_round_trip_preserves_pose_and_score() -> None: + candidate = GraspCandidate(Pose(0.4, -0.2, 0.3), 0.75) + + decoded = GraspCandidate.decode(candidate.encode()) + + assert decoded.pose.position.x == 0.4 + assert decoded.pose.position.y == -0.2 + assert decoded.pose.position.z == 0.3 + assert decoded.score == 0.75 + + +def test_grasp_candidate_rejects_non_finite_score() -> None: + with pytest.raises(ValueError, match="score must be finite"): + GraspCandidate(score=float("nan")) + + +def test_grasp_candidate_array_round_trip_preserves_header_and_order() -> None: + candidates = [ + GraspCandidate(Pose(0.1, 0.0, 0.2), 0.9), + GraspCandidate(Pose(0.2, 0.0, 0.2), 0.7), + ] + proposals = GraspCandidateArray(Header(123.0, "world"), candidates) + + decoded = GraspCandidateArray.decode(proposals.encode()) + + assert decoded.header.timestamp == 123.0 + assert decoded.header.frame_id == "world" + assert [candidate.score for candidate in decoded] == [0.9, 0.7] + assert [candidate.pose.position.x for candidate in decoded] == [0.1, 0.2] diff --git a/dimos/robot/all_blueprints.py b/dimos/robot/all_blueprints.py index c67b10b549..8b20bd3dcc 100644 --- a/dimos/robot/all_blueprints.py +++ b/dimos/robot/all_blueprints.py @@ -151,6 +151,8 @@ "unitree-go2-webrtc-keyboard-teleop": "dimos.robot.unitree.go2.blueprints.basic.unitree_go2_webrtc_keyboard_teleop:unitree_go2_webrtc_keyboard_teleop", "unitree-go2-webrtc-rage-keyboard-teleop": "dimos.robot.unitree.go2.blueprints.basic.unitree_go2_webrtc_rage_keyboard_teleop:unitree_go2_webrtc_rage_keyboard_teleop", "unity-sim": "dimos.simulation.unity.blueprint:unity_sim", + "xarm-graspgenx": "dimos.robot.manipulators.xarm.blueprints.graspgenx:xarm_graspgenx", + "xarm-graspgenx-agent": "dimos.robot.manipulators.xarm.blueprints.agentic:xarm_graspgenx_agent", "xarm-perception": "dimos.robot.manipulators.xarm.blueprints.perception:xarm_perception", "xarm-perception-agent": "dimos.robot.manipulators.xarm.blueprints.agentic:xarm_perception_agent", "xarm-perception-sim": "dimos.robot.manipulators.xarm.blueprints.simulation:xarm_perception_sim", diff --git a/dimos/robot/manipulators/common/agent_prompts.py b/dimos/robot/manipulators/common/agent_prompts.py index a010bf8018..31e02c1507 100644 --- a/dimos/robot/manipulators/common/agent_prompts.py +++ b/dimos/robot/manipulators/common/agent_prompts.py @@ -59,14 +59,15 @@ ## Pick & Place - **pick **: Pick up a detected object by name. Use the EXACT name from \ look/scan_objects output. When duplicates exist, pass the object_id shown in brackets \ -(e.g. [id=abc12345]). Example: "pick the cup", "grab the spray can" +(e.g. [id=abc12345]). On GraspGenX-enabled stacks, pick ranks learned grasp proposals, \ +checks motion feasibility before moving, and can verify calibrated closure feedback. Example: \ +"pick the cup", "grab the spray can" - **place **: Place a held object at explicit world-frame coordinates. \ Example: "place it at 0.4, 0.3, 0.1" - **drop_on **: Drop a held object onto another detected object. \ Automatically compensates for camera occlusion. Example: "drop it in the bowl", \ "put it on the box" - **place_back**: Return a held object to its original pick position. -- **pick_and_place **: Pick then place in one command. ## Motion - **move_to_pose [roll pitch yaw]**: Move end-effector to an absolute \ @@ -99,13 +100,18 @@ - NEVER open the gripper while holding an object unless the user asks or you are \ executing place/drop_on. The gripper stays closed during movement. - After pick or place, return to init with **go_init** unless another action follows. +- If pick reports that the object may be held, do not open the gripper automatically. \ +Report the failure phase and ask the user before releasing or recovering. +- If pick fails before closure, call **reset** if the robot entered FAULT, then \ +**scan_objects** before retrying. Do not clear all perception obstacles merely to force \ +a plan through a changed scene. # Coordinate System World frame (meters): X = forward, Y = left, Z = up. Z = 0 is robot base. Typical working area: X 0.3-0.7, Y -0.5 to 0.5, Z 0.05-0.5. # Error Recovery -If planning fails with COLLISION_AT_START: call **clear_perception_obstacles**, then \ -**reset**, then retry. -After any planning failure, call **reset** before more planning or motion. +If planning fails with COLLISION_AT_START, inspect or rescan the scene. Clear perception \ +obstacles only when they are known to be stale. After any robot motion fault, call \ +**reset** before more planning or motion. """ diff --git a/dimos/robot/manipulators/xarm/blueprints/agentic.py b/dimos/robot/manipulators/xarm/blueprints/agentic.py index 073ede3607..465370a77b 100644 --- a/dimos/robot/manipulators/xarm/blueprints/agentic.py +++ b/dimos/robot/manipulators/xarm/blueprints/agentic.py @@ -24,6 +24,7 @@ MANIPULATION_AGENT_SYSTEM_PROMPT, ) from dimos.robot.manipulators.xarm.blueprints.basic import xarm7_planner_coordinator +from dimos.robot.manipulators.xarm.blueprints.graspgenx import xarm_graspgenx from dimos.robot.manipulators.xarm.blueprints.perception import xarm_perception from dimos.robot.manipulators.xarm.blueprints.simulation import xarm_perception_sim @@ -39,6 +40,12 @@ McpClient.blueprint(system_prompt=MANIPULATION_AGENT_SYSTEM_PROMPT), ) +xarm_graspgenx_agent = autoconnect( + xarm_graspgenx, + McpServer.blueprint(), + McpClient.blueprint(system_prompt=MANIPULATION_AGENT_SYSTEM_PROMPT), +) + xarm_perception_sim_agent = autoconnect( xarm_perception_sim, McpServer.blueprint(), diff --git a/dimos/robot/manipulators/xarm/blueprints/graspgenx.py b/dimos/robot/manipulators/xarm/blueprints/graspgenx.py new file mode 100644 index 0000000000..c753194c97 --- /dev/null +++ b/dimos/robot/manipulators/xarm/blueprints/graspgenx.py @@ -0,0 +1,61 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""GraspGenX-enabled real-hardware xArm perception blueprint.""" + +from __future__ import annotations + +import math + +from dimos.core.coordination.blueprints import autoconnect +from dimos.manipulation.grasping.grasp_gen_x import GraspGenXModule +from dimos.manipulation.pick_and_place_module import PickAndPlaceModule +from dimos.robot.manipulators.xarm.blueprints.perception import xarm_perception +from dimos.robot.manipulators.xarm.config import make_xarm7_model_config +from dimos.robot.manipulators.xarm.grasp_config import make_xarm_graspgenx_config + +_graspgenx_config = make_xarm_graspgenx_config() + +xarm_graspgenx = autoconnect( + xarm_perception, + PickAndPlaceModule.blueprint( + robots=[ + make_xarm7_model_config( + name="arm", + add_gripper=True, + pitch=math.radians(45), + tf_extra_links=["link7"], + ) + ], + planning_timeout=10.0, + visualization={"backend": "meshcat"}, + floor_z=-0.02, + heuristic_grasp_fallback=False, + planning_frame="world", + grasp_approach_vector=(0.0, 0.0, -1.0), + grasp_verification={ + # Enable only after completing the hardware calibration recorded + # in the grasp-pipeline OpenSpec change. + "enabled": False, + "open_position": 0.85, + "closed_position": 0.0, + "held_threshold": 0.02, + "timeout": 2.0, + "poll_interval": 0.05, + }, + ), + GraspGenXModule.blueprint( + **_graspgenx_config.model_dump(exclude={"rpc_transport", "tf_transport", "g"}) + ), +).global_config(n_workers=5) diff --git a/dimos/robot/manipulators/xarm/blueprints/perception.py b/dimos/robot/manipulators/xarm/blueprints/perception.py index f187e7ab50..ead451ca37 100644 --- a/dimos/robot/manipulators/xarm/blueprints/perception.py +++ b/dimos/robot/manipulators/xarm/blueprints/perception.py @@ -45,6 +45,7 @@ planning_timeout=10.0, visualization={"backend": "meshcat"}, floor_z=-0.02, + heuristic_grasp_fallback=True, ), RealSenseCamera.blueprint( base_frame_id="link7", diff --git a/dimos/robot/manipulators/xarm/blueprints/simulation.py b/dimos/robot/manipulators/xarm/blueprints/simulation.py index fb6e21f09a..7e7d2fa1de 100644 --- a/dimos/robot/manipulators/xarm/blueprints/simulation.py +++ b/dimos/robot/manipulators/xarm/blueprints/simulation.py @@ -36,6 +36,7 @@ robots=[make_xarm7_sim_robot_config()], planning_timeout=10.0, visualization={"backend": "meshcat"}, + heuristic_grasp_fallback=True, ), MujocoSimModule.blueprint(**make_xarm7_sim_module_kwargs(XARM7_SIM_PATH)), ObjectSceneRegistrationModule.blueprint(target_frame="world"), diff --git a/dimos/robot/manipulators/xarm/blueprints/test_graspgenx.py b/dimos/robot/manipulators/xarm/blueprints/test_graspgenx.py new file mode 100644 index 0000000000..9cde5043e8 --- /dev/null +++ b/dimos/robot/manipulators/xarm/blueprints/test_graspgenx.py @@ -0,0 +1,80 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from typing import Any + +from dimos.agents.mcp.mcp_client import McpClient +from dimos.agents.mcp.mcp_server import McpServer +from dimos.core.coordination.blueprints import Blueprint +from dimos.manipulation.grasping.grasp_gen_x import GraspGenXModule +from dimos.manipulation.pick_and_place_module import ( + PickAndPlaceModule, + PickAndPlaceModuleConfig, +) +from dimos.perception.experimental.object_scene_registration import ( + ObjectSceneRegistrationModule, +) +from dimos.robot.manipulators.xarm.blueprints.agentic import xarm_graspgenx_agent +from dimos.robot.manipulators.xarm.blueprints.graspgenx import xarm_graspgenx +from dimos.robot.manipulators.xarm.blueprints.perception import xarm_perception +from dimos.robot.manipulators.xarm.grasp_config import ( + XARM_GRASP_FRAME_TO_TCP, + XARM_GRIPPER_SWEEP, + make_xarm_graspgenx_config, +) + + +def _module_kwargs(blueprint: Blueprint, module_type: type) -> dict[str, Any]: + return next(atom.kwargs for atom in blueprint.blueprints if atom.module is module_type) + + +def _module_count(blueprint: Blueprint, module_type: type) -> int: + return sum(atom.module is module_type for atom in blueprint.active_blueprints) + + +def test_xarm_graspgenx_geometry_is_explicit_and_import_safe() -> None: + config = make_xarm_graspgenx_config() + + assert config.gripper == XARM_GRIPPER_SWEEP + assert config.grasp_frame_to_tcp == XARM_GRASP_FRAME_TO_TCP + assert config.gripper.extents_open == (0.085, 0.032, 0.067) + assert config.gripper.extents_half_open == (0.0425, 0.032, 0.067) + assert config.gripper.fingertip_depth == 0.162 + assert config.grasp_frame_to_tcp[2][3] == 0.172 + + +def test_existing_xarm_perception_keeps_explicit_heuristic_fallback() -> None: + config = PickAndPlaceModuleConfig(**_module_kwargs(xarm_perception, PickAndPlaceModule)) + + assert config.heuristic_grasp_fallback is True + assert _module_count(xarm_perception, GraspGenXModule) == 0 + + +def test_xarm_graspgenx_composes_one_provider_of_each_kind() -> None: + config = PickAndPlaceModuleConfig(**_module_kwargs(xarm_graspgenx, PickAndPlaceModule)) + + assert _module_count(xarm_graspgenx, ObjectSceneRegistrationModule) == 1 + assert _module_count(xarm_graspgenx, GraspGenXModule) == 1 + assert _module_count(xarm_graspgenx, PickAndPlaceModule) == 1 + assert config.heuristic_grasp_fallback is False + assert config.grasp_approach_vector == (0.0, 0.0, -1.0) + assert config.grasp_verification.enabled is False + + +def test_xarm_graspgenx_agent_composes_one_mcp_pair() -> None: + assert _module_count(xarm_graspgenx_agent, McpServer) == 1 + assert _module_count(xarm_graspgenx_agent, McpClient) == 1 + assert _module_count(xarm_graspgenx_agent, ObjectSceneRegistrationModule) == 1 + assert _module_count(xarm_graspgenx_agent, GraspGenXModule) == 1 + assert _module_count(xarm_graspgenx_agent, PickAndPlaceModule) == 1 diff --git a/dimos/robot/manipulators/xarm/grasp_config.py b/dimos/robot/manipulators/xarm/grasp_config.py new file mode 100644 index 0000000000..16b572641d --- /dev/null +++ b/dimos/robot/manipulators/xarm/grasp_config.py @@ -0,0 +1,52 @@ +# Copyright 2026 Dimensional Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""GraspGenX geometry for the UFACTORY xArm gripper.""" + +from __future__ import annotations + +from dimos.manipulation.grasping.grasp_gen_x import ( + GraspGenXConfig, + SweepVolumeGripperConfig, +) + +# Geometry was derived from UFACTORY's xarm_ros gripper URDF and collision +# meshes at commit 0b5118eb6bf664fc3891c14b203e6ecbd5095dca: +# - link_tcp is 0.172 m along +Z from xarm_gripper_base_link +# - the inner finger volume is approximately 0.085 x 0.032 x 0.067 m +# The model's grasp frame is the gripper base; DimOS plans for link_tcp. +XARM_GRASP_FRAME_TO_TCP = ( + (1.0, 0.0, 0.0, 0.0), + (0.0, 1.0, 0.0, 0.0), + (0.0, 0.0, 1.0, 0.172), + (0.0, 0.0, 0.0, 1.0), +) + +XARM_GRIPPER_SWEEP = SweepVolumeGripperConfig( + extents_open=(0.085, 0.032, 0.067), + offset_open=(0.0, 0.0, 0.1285), + extents_half_open=(0.0425, 0.032, 0.067), + offset_half_open=(0.0, 0.0, 0.1285), + fingertip_depth=0.162, + family="revolute_2f", +) + + +def make_xarm_graspgenx_config() -> GraspGenXConfig: + """Return the import-safe learned-grasp deployment configuration.""" + return GraspGenXConfig( + gripper=XARM_GRIPPER_SWEEP, + grasp_frame_to_tcp=XARM_GRASP_FRAME_TO_TCP, + max_candidates=100, + ) diff --git a/dimos/robot/test_all_blueprints.py b/dimos/robot/test_all_blueprints.py index 4a6692a663..659fc353d7 100644 --- a/dimos/robot/test_all_blueprints.py +++ b/dimos/robot/test_all_blueprints.py @@ -67,6 +67,8 @@ "xarm-perception-agent", "xarm-perception-sim", "xarm-perception-sim-agent", + "xarm-graspgenx", + "xarm-graspgenx-agent", "xarm7-planner-coordinator", "xarm7-planner-coordinator-agent", } diff --git a/docs/capabilities/manipulation/agentic.md b/docs/capabilities/manipulation/agentic.md index 3b3b03f9ef..6d57f2a1b5 100644 --- a/docs/capabilities/manipulation/agentic.md +++ b/docs/capabilities/manipulation/agentic.md @@ -43,6 +43,56 @@ uv run dimos stop Use `dimos log -f` to follow the log while the run is active. +## Learned grasp-to-pick pipeline + +The real-hardware `xarm-graspgenx-agent` blueprint adds GraspGenX proposals to +the xArm perception stack. Install the optional runtime and start it with: + +```bash +uv sync --extra graspgenx --extra manipulation --inexact +uv run dimos run xarm-graspgenx-agent +``` + +`pick` remains the only high-level picking tool. It resolves one current +object, obtains that object's planning-frame point cloud, requests ranked +GraspGenX candidates, and rejects candidates that fail pre-grasp, grasp, or +retreat inverse kinematics. During planning, the selected target is +temporarily removed from the collision scene while all other obstacles remain +active. The selected candidate then runs through prepare, approach, grasp, +close, verify, and retreat phases. + +Use the stable object ID returned by `scan_objects` whenever names are +ambiguous. A name is accepted only when it identifies exactly one current +detection; an object-ID prefix must also be unique. Existing +`xarm-perception` and `xarm-perception-sim` blueprints retain their explicit +heuristic grasp fallback and do not load the optional GraspGenX runtime. + +The learned pipeline configuration lives in +`dimos/robot/manipulators/xarm/grasp_config.py`. It records the xArm gripper +sweep volume and the transform from GraspGenX's gripper frame to the planned +TCP. `PickAndPlaceModuleConfig` controls the planning frame, maximum point +cloud age, candidate-check limit, TCP approach direction, approach/retreat +offsets, heuristic fallback, and closure-feedback verification thresholds. +Changing the frame transform, approach direction, or closure threshold +requires robot-specific calibration. + +Failures are phase-specific and stop motion immediately. Before closure, a +failed transaction leaves the gripper in its current safe state. After a +successful close command, failures never automatically reopen the gripper; +the result includes `object_may_be_held=true`, and an operator or agent should +inspect state before issuing another motion. Target collision geometry is +restored on every exit path, and restoration errors are reported without +hiding the primary failure. + +The current verification is a closure-position proxy: an xArm gripper that +stops above the calibrated empty-close threshold is treated as holding +something. It does not measure grasp force, detect slip, or prove that the +intended object was acquired. Force/torque or tactile feedback is required for +those stronger guarantees. The shipped learned-grasp blueprint keeps this +proxy disabled until the open, empty-close, and representative held-object +positions have been measured on the target xArm; enable +`grasp_verification.enabled` only after recording that calibration. + ## Daily interaction For normal interactive use, start the human-friendly terminal client: diff --git a/openspec/changes/add-grasp-pipeline-skill/.openspec.yaml b/openspec/changes/add-grasp-pipeline-skill/.openspec.yaml new file mode 100644 index 0000000000..f205fc727f --- /dev/null +++ b/openspec/changes/add-grasp-pipeline-skill/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-07-29 diff --git a/openspec/changes/add-grasp-pipeline-skill/README.md b/openspec/changes/add-grasp-pipeline-skill/README.md new file mode 100644 index 0000000000..29d33b2729 --- /dev/null +++ b/openspec/changes/add-grasp-pipeline-skill/README.md @@ -0,0 +1,3 @@ +# add-grasp-pipeline-skill + +Add an end-to-end manipulation skill that resolves an object point cloud, requests GraspGenX proposals, validates and ranks feasible candidates, and executes a safe pick. diff --git a/openspec/changes/add-grasp-pipeline-skill/design.md b/openspec/changes/add-grasp-pipeline-skill/design.md new file mode 100644 index 0000000000..5682ce8d87 --- /dev/null +++ b/openspec/changes/add-grasp-pipeline-skill/design.md @@ -0,0 +1,153 @@ +## Context + +GraspGenX is an import-safe, dedicated-worker module implementing `GraspGenSpec.propose_grasps(PointCloud2) -> GraspCandidateArray`. Object perception already implements `ObjectSceneRegistrationSpec`, including stable-ID/name point-cloud lookup, and emits world-frame `DetObject` instances consumed by `PickAndPlaceModule`. + +The current `pick` skill already owns planning, execution, gripper control, perception obstacle integration, and `place_back` state, but `_generate_grasps_for_pick` produces a single hand-authored pose. Its sequence begins moving after only the pre-grasp plan succeeds, uses fixed waits for gripper commands, and reports success without checking whether an object prevented full closure. + +The natural missing seam is orchestration inside `PickAndPlaceModule`: + +```text +object name / id + | + v +ObjectSceneRegistrationSpec -----> object PointCloud2 (world) + | + v +GraspGenSpec --------------------> ranked TCP candidates + | + v +PickAndPlaceModule + resolve -> validate -> feasibility gate -> execute -> verify -> retreat + | | + +------ planning world / coordinator -------+ +``` + +The model worker remains independent and optional. The high-level skill owns the transaction because it already owns manipulation state and can guarantee cleanup across planning, gripper, and obstacle mutations. + +## Goals / Non-Goals + +**Goals:** + +- Make `pick` a complete learned-grasp pipeline with precise, testable phase and failure semantics. +- Reuse the existing perception, proposal, planning, coordinator, and `SkillResult` interfaces. +- Reject bad candidates before physical motion where possible. +- Preserve non-target collision checking and guarantee planning-scene cleanup. +- Verify physical closure using gripper feedback on the initial xArm integration. +- Keep the high-level agent interface stable. + +**Non-Goals:** + +- Retraining, fine-tuning, or changing GraspGenX inference. +- Adding grasp-quality calibration across proposal backends. +- Visual-servoing or closed-loop pose correction during the final approach. +- Force/torque-based grasp verification, slip detection, or automatic regrasp after contact. +- General attached-object collision geometry during subsequent place motions; that should be a follow-up capability. +- Enabling GraspGenX on every manipulator blueprint in the first change. + +## Decisions + +### 1. Deepen `PickAndPlaceModule` instead of adding a peer skill container + +`PickAndPlaceModule` will declare injected perception and grasp proposal Spec attributes and will orchestrate the transaction behind its existing `pick` skill. This keeps planning state, obstacle state, execution, gripper control, and `_last_pick_pose` under one lifecycle owner. + +Alternative: create a separate `GraspPipelineSkillContainer` that calls manipulation RPCs. Rejected because the current manipulation API has no transaction-level Spec, would expose partially coordinated state across RPC threads, and would duplicate cleanup and error translation. + +### 2. Preserve `pick` as the public skill + +The existing signature remains `pick(object_name, object_id=None, robot_name=None)`. Internally, candidate generation becomes a provider strategy: injected `GraspGenSpec` first, with the existing heuristic generator only when explicitly enabled in `PickAndPlaceModuleConfig`. + +Alternative: add `grasp_pick` or `pick_with_graspgenx`. Rejected because agents would have to choose between overlapping high-level skills and the orchestration is backend-independent even though GraspGenX is the first provider. + +### 3. Resolve through the perception RPC, not the local detection snapshot + +The snapshot remains useful for agent display and obstacle synchronization, but the pipeline obtains the proposal input using `ObjectSceneRegistrationSpec` by stable ID or unique name. The returned point cloud and proposal header must match the configured planning frame (initially `world`). Frame mismatch is an error; this change does not add a hidden TF dependency. + +Name-only lookup must first establish uniqueness from the current detection snapshot. This avoids the existing perception RPC's “first matching name” behavior silently selecting the wrong duplicate. + +### 4. Separate candidate feasibility from physical execution + +Candidates remain in generator score order. For each candidate up to `max_grasp_candidates_to_check`, the pipeline: + +1. validates finite rigid-pose data and frame agreement; +2. derives pre-grasp and retreat poses using the configured approach-axis offset; +3. checks IK/collision feasibility for all three targets without dispatching motion; +4. chooses the first candidate passing the gate. + +The actual plans are regenerated from live state immediately before each phase because stored plans become stale after execution. A planning or execution failure after motion begins terminates the transaction rather than jumping to another candidate from a changed robot state. + +Alternative: attempt candidates sequentially and retry after any failure. Rejected because after the first approach the robot is no longer at the common evaluated start state, making retry safety and cleanup ambiguous. + +### 5. Treat target obstacle exclusion as transaction state + +Before feasibility checks, the pipeline calls the existing targeted `WorldMonitor.remove_object_obstacle(object_id)` path. Other perception and static obstacles remain. A `try/finally` transaction boundary refreshes perception obstacles on every return path. + +The obstacle monitor can receive live updates concurrently, so the implementation must ensure the target is not re-added during the exclusion window. The preferred extension is a scoped suppression API owned by `WorldObstacleMonitor` (for example, an object-ID suppression context managed under its existing lock), rather than repeatedly deleting the obstacle from orchestration code. + +Alternative: clear all perception obstacles. Rejected because it removes collision protection for the rest of the scene. Permanently delete the target obstacle. Rejected because failure paths would leave the planning world inconsistent. + +### 6. Model the pipeline as an explicit transaction + +A private transaction object records the selected object, proposal source, current phase, candidate rank/score, target-suppression handle, closure state, and cleanup status. A module lock rejects concurrent `pick` calls. The phases are: + +```text +RESOLVE -> PROPOSE -> SELECT -> PREPARE -> APPROACH -> GRASP + | + v + CLOSE -> VERIFY -> RETREAT -> DONE +``` + +No automatic rollback motion is promised. Before gripper closure, failures leave the gripper state explicit in the result. After closure, failures never auto-open the gripper because an object may be held. + +### 7. Use feedback-based closure verification with robot-specific configuration + +The initial xArm blueprint configures: + +- open and closed command endpoints in the units already expected by the coordinator path; +- a held-object closure threshold and comparison direction; +- command and verification timeouts plus polling interval. + +The pipeline first checks the close command result, then polls `get_gripper`. Reaching the empty-closed region is a verification failure; remaining beyond the held threshold is success. Configuration validation ensures the threshold lies between the open and closed endpoints. + +This is a contact proxy, not proof against slip. The result should say “grasp verified by gripper closure feedback,” not claim force or object identity verification. + +Alternative: fixed sleep followed by unconditional success. Rejected because it cannot distinguish an accepted command from a successful physical pick. + +### 8. Return structured phase-specific failures + +Extend manipulation errors with at least: + +- `GRASP_PROVIDER_UNAVAILABLE` +- `GRASP_INPUT_INVALID` +- `GRASP_FRAME_MISMATCH` +- `GRASP_VERIFICATION_FAILED` +- `PICK_BUSY` + +Existing `OBJECT_NOT_DETECTED`, `GRASP_GENERATION_FAILED`, `GRASP_ATTEMPTS_EXHAUSTED`, `PLANNING_FAILED`, `GRIPPER_FAILED`, execution errors, and timeouts remain applicable. Human-readable details include phase, candidate rank/score when selected, and whether the gripper may hold an object. + +## Risks / Trade-offs + +- [Single-view point clouds can produce geometrically plausible but poor grasps] → retain score ordering, validate scene feasibility, expose candidate rank/score, and leave visual servoing/regrasp for follow-up. +- [Gripper aperture is an imperfect grasp signal, especially for thin objects] → make thresholds robot-specific, test boundary behavior, and describe verification as a closure proxy. +- [Planning feasibility checks may be expensive across many GPU proposals] → cap candidates checked, stop at the first feasible candidate, and record rejection metrics for tuning. +- [The target can be re-added by asynchronous perception during a pick] → add scoped suppression inside the obstacle monitor under its lock and test live-update behavior. +- [A target-free collision world permits intended finger/object contact but cannot model post-grasp payload collisions] → keep all non-target obstacles and explicitly defer attached-object geometry. +- [GraspGenX increases GPU memory and startup time] → retain a dedicated worker, lazy optional runtime imports, and blueprint-level opt-in. +- [Planning can still fail after an earlier feasibility gate because the robot/world changed] → regenerate plans from live state and stop safely rather than retrying from an unanalysed state. + +## Migration Plan + +1. Add configuration, error codes, and private transaction/candidate helpers behind the unchanged `pick` signature. +2. Add scoped target-obstacle suppression and unit tests without enabling it in shipped blueprints. +3. Wire the perception and proposal Specs into `PickAndPlaceModule`; keep heuristic fallback explicitly enabled in legacy blueprints during transition. +4. Add a distinct GraspGenX-enabled xArm perception blueprint with xArm-specific gripper sweep/TCP and verification configuration, then compose its agentic variant. Keep the existing blueprint dependency footprint unchanged. +5. Validate in deterministic unit tests, recorded/replay perception, MuJoCo where sensor support permits, and finally real xArm hardware with a guarded test matrix. +6. Update the agent prompt, blueprint registry if a new runnable blueprint is introduced, and manipulation documentation. + +Rollback is blueprint-level: remove the GraspGenX module and restore explicit heuristic fallback. The public `pick` signature does not require caller migration. + +## Open Questions + +- What xArm closure threshold has been validated for the physical gripper, and does it need object-width-aware tolerance? +- Does the current perception obstacle monitor need to freeze only the target ID, or should it snapshot all obstacles for the short execution window? +- Is candidate feasibility via existing IK/collision APIs sufficiently predictive, or should the first version generate full approach/grasp/retreat paths in a cloned planning context? +- Which approach axis encoded by the GraspGenX TCP transform should define pre-grasp and retreat offsets for the configured xArm gripper? diff --git a/openspec/changes/add-grasp-pipeline-skill/proposal.md b/openspec/changes/add-grasp-pipeline-skill/proposal.md new file mode 100644 index 0000000000..c4b8d6e2ae --- /dev/null +++ b/openspec/changes/add-grasp-pipeline-skill/proposal.md @@ -0,0 +1,30 @@ +## Why + +GraspGenX can now produce ranked grasp poses, but the agent-facing `pick` skill still uses one heuristic pose and has no end-to-end path from a detected object to a verified physical pick. A pipeline is needed now to connect object point-cloud lookup, learned proposals, motion feasibility, collision-world handling, gripper actuation, and clear recovery semantics. + +## What Changes + +- Upgrade the existing `pick` skill to resolve a unique detected object and obtain its world-frame point cloud through the perception RPC interface. +- Request ranked TCP grasp candidates through `GraspGenSpec`, preserving the generator's score order while rejecting invalid or motion-infeasible candidates. +- Execute a safe pick sequence: pre-grasp approach, gripper open, grasp, gripper close, and retreat. +- Temporarily exclude only the target object from planning collisions while preserving all other scene obstacles, and restore a consistent planning scene on every exit path. +- Verify grasp closure using configured gripper feedback when available and return structured failure codes that distinguish perception, proposal, feasibility, execution, and verification failures. +- Keep the heuristic grasp path available only as an explicit configuration fallback for blueprints that do not include a grasp proposal module. +- Add a GraspGenX-enabled xArm perception manipulation blueprint with gripper-specific configuration, leaving the existing non-GPU blueprint available, and update the manipulation agent prompt to describe the learned-pick behavior. + +## Capabilities + +### New Capabilities + +- `grasp-pipeline-skill`: End-to-end behavior and failure semantics for resolving an object, proposing and selecting feasible grasps, executing a pick, and verifying the result. + +### Modified Capabilities + +None. + +## Impact + +- Affected modules: `PickAndPlaceModule`, `GraspGenSpec`, `ObjectSceneRegistrationSpec`, manipulation error types, and xArm perception blueprints/prompts. +- The new xArm learned-pick blueprint requires the `graspgenx` optional dependency, a dedicated GraspGenX worker, and robot-specific sweep-volume/TCP configuration; existing xArm blueprints remain runnable without that extra. +- Existing `pick(object_name, object_id, robot_name)` callers remain source-compatible; observed candidate selection and failure results become more precise. +- No change is proposed to GraspGenX inference itself or to generic motion-planner algorithms. diff --git a/openspec/changes/add-grasp-pipeline-skill/specs/grasp-pipeline-skill/spec.md b/openspec/changes/add-grasp-pipeline-skill/specs/grasp-pipeline-skill/spec.md new file mode 100644 index 0000000000..7ecf052a06 --- /dev/null +++ b/openspec/changes/add-grasp-pipeline-skill/specs/grasp-pipeline-skill/spec.md @@ -0,0 +1,92 @@ +## ADDED Requirements + +### Requirement: Unique object resolution +The `pick` skill SHALL resolve exactly one detected object before requesting grasp proposals. It SHALL prefer an explicitly supplied stable object ID, SHALL reject ambiguous ID prefixes or names, and SHALL return `OBJECT_NOT_DETECTED` when no object matches. + +#### Scenario: Object ID selects one of several same-named objects +- **WHEN** the caller supplies an object ID that uniquely identifies one detected object +- **THEN** the pipeline uses that object's point cloud regardless of other objects with the same name + +#### Scenario: Object name is ambiguous +- **WHEN** the caller supplies only a name and multiple current detections match it +- **THEN** the pipeline performs no robot motion and returns a failure that asks the caller to provide an object ID + +### Requirement: Proposal input and frame contract +The pipeline SHALL retrieve the selected object's `PointCloud2` through `ObjectSceneRegistrationSpec`, SHALL reject empty or stale input according to configured limits, and SHALL require the proposal frame to match the manipulation planning frame. It MUST NOT silently interpret a candidate in a different frame. + +#### Scenario: Valid world-frame point cloud +- **WHEN** perception returns a non-empty, sufficiently recent object point cloud in the manipulation planning frame +- **THEN** the pipeline passes that cloud unchanged to `GraspGenSpec.propose_grasps` + +#### Scenario: Proposal frame differs from planning frame +- **WHEN** the returned candidate array identifies a frame other than the configured manipulation planning frame +- **THEN** the pipeline performs no robot motion and returns a frame-mismatch failure + +### Requirement: Ranked feasibility selection +The pipeline SHALL examine candidates in descending generator-score order, up to a configurable attempt limit. It SHALL reject non-finite or malformed poses and candidates whose pre-grasp, grasp, or retreat targets fail kinematic or collision feasibility checks. Generator scores SHALL be treated only as relative ranking values, not calibrated probabilities. + +#### Scenario: Highest-scored candidate is infeasible +- **WHEN** the first candidate cannot satisfy approach or grasp feasibility and a lower-scored candidate can +- **THEN** the pipeline selects the first feasible lower-scored candidate without moving for the rejected candidate + +#### Scenario: No candidate is feasible +- **WHEN** every candidate within the configured attempt limit fails validation or feasibility +- **THEN** the pipeline performs no gripper closure, leaves the robot in a safe pre-pick state, and returns `GRASP_ATTEMPTS_EXHAUSTED` with rejection counts by reason + +### Requirement: Target-aware collision scene +The pipeline SHALL keep non-target scene obstacles active while checking and executing a pick. It SHALL exclude only the selected target object from collision checking for the grasp approach and SHALL restore a consistent perception-derived planning scene on success, failure, cancellation, or exception. + +#### Scenario: Target is registered as an obstacle +- **WHEN** the selected object is present in the planning world as a perception obstacle +- **THEN** the pipeline removes that object's obstacle before grasp feasibility checks while retaining all other object and static obstacles + +#### Scenario: Execution fails after target exclusion +- **WHEN** any later planning, execution, gripper, verification, or retreat step fails +- **THEN** cleanup refreshes or restores the perception obstacle state before the skill returns + +### Requirement: Safe pick execution +For a selected feasible candidate, the pipeline SHALL execute the ordered phases `PREPARE`, `APPROACH`, `GRASP`, `CLOSE`, `VERIFY`, and `RETREAT`. It SHALL stop at the first failed phase, SHALL report that phase in the result, and MUST NOT open the gripper automatically after closure because the robot may be holding the object. + +#### Scenario: Successful pick sequence +- **WHEN** all motion plans execute, gripper commands are accepted, verification succeeds, and retreat completes +- **THEN** the skill returns success including the selected candidate rank and score and stores the grasp pose for `place_back` + +#### Scenario: Retreat fails after closure +- **WHEN** gripper closure and verification succeed but retreat planning or execution fails +- **THEN** the skill returns a retreat failure, leaves the gripper closed, and reports that the object may still be held + +### Requirement: Grasp verification +The learned-pick blueprint SHALL configure gripper-feedback verification. Verification SHALL poll feedback until a configurable timeout and SHALL distinguish an object-blocked closure from a fully closed empty gripper using robot-specific command units and thresholds. + +#### Scenario: Feedback indicates an object is held +- **WHEN** the final gripper position remains on the configured held-object side of the closure threshold before timeout +- **THEN** verification succeeds and the pipeline proceeds to retreat + +#### Scenario: Feedback indicates an empty close +- **WHEN** the gripper reaches the configured empty-closed region +- **THEN** the pipeline returns `GRASP_VERIFICATION_FAILED`, leaves the gripper closed, and does not report a successful pick + +### Requirement: Explicit heuristic fallback +Blueprints without a grasp proposal provider SHALL fail learned-pick requests by default. A blueprint MAY explicitly enable the existing heuristic pose generator as a fallback, and the skill result SHALL identify when that fallback was used. + +#### Scenario: Grasp provider is unavailable and fallback is disabled +- **WHEN** `pick` is called without an injected `GraspGenSpec` +- **THEN** the skill performs no motion and returns `GRASP_PROVIDER_UNAVAILABLE` + +#### Scenario: Grasp provider is unavailable and fallback is enabled +- **WHEN** `pick` is called without an injected `GraspGenSpec` on a blueprint that explicitly enables heuristic fallback +- **THEN** the pipeline uses the heuristic candidate path and identifies the proposal source in its result + +### Requirement: Single active pick transaction +The module SHALL allow at most one pick pipeline transaction at a time. A concurrent request SHALL be rejected without changing motion, gripper, proposal, or planning-scene state. + +#### Scenario: Concurrent pick request +- **WHEN** a second `pick` call arrives while another pick transaction is active +- **THEN** the second call returns a busy failure and does not enter any pipeline phase + +### Requirement: Blueprint and agent exposure +The xArm perception manipulation stack SHALL compose `PickAndPlaceModule`, `ObjectSceneRegistrationModule`, and `GraspGenXModule` with compatible world-frame and gripper/TCP configuration. The agent prompt SHALL continue to expose `pick` as the single high-level picking skill and SHALL describe its object-ID disambiguation and failure recovery behavior. + +#### Scenario: Learned-pick blueprint is built +- **WHEN** the xArm learned-pick blueprint is constructed with the `graspgenx` extra installed +- **THEN** blueprint Spec injection resolves one perception provider and one grasp proposal provider for the pick module diff --git a/openspec/changes/add-grasp-pipeline-skill/tasks.md b/openspec/changes/add-grasp-pipeline-skill/tasks.md new file mode 100644 index 0000000000..a726ba6e48 --- /dev/null +++ b/openspec/changes/add-grasp-pipeline-skill/tasks.md @@ -0,0 +1,51 @@ +## 1. Contracts and Configuration + +- [x] 1.1 Add phase-specific grasp pipeline error codes to `ManipulationSkillError` and unit-test their `SkillResult` serialization/logging behavior. +- [x] 1.2 Add validated `PickAndPlaceModuleConfig` fields for provider fallback, planning frame, input age, candidate limit, pre-grasp/retreat offsets, and gripper-feedback verification. +- [x] 1.3 Declare optional injected `ObjectSceneRegistrationSpec` and `GraspGenSpec` dependencies on `PickAndPlaceModule`, and add blueprint build tests for present, absent, and ambiguous providers. +- [x] 1.4 Define private typed transaction, phase, candidate, rejection, and verification-result models so state that changes together is not stored in parallel fields. + +## 2. Target-Aware Planning Scene + +- [x] 2.1 Add a scoped target-object suppression API to `WorldObstacleMonitor` and its `WorldMonitor` facade using the existing monitor lock. +- [x] 2.2 Ensure live perception updates cannot re-add a suppressed target while other object obstacles continue to add/update normally. +- [x] 2.3 Ensure suppression exit refreshes or restores the target on normal return, exception, cancellation, and partial setup failure. +- [x] 2.4 Add deterministic unit tests covering nested/duplicate suppression requests, concurrent perception updates, failed obstacle mutations, and cleanup. + +## 3. Object Resolution and Candidate Selection + +- [x] 3.1 Implement unique object resolution by stable ID or unambiguous current name, returning actionable failures without motion. +- [x] 3.2 Retrieve and validate the selected object's point cloud through `ObjectSceneRegistrationSpec`, including non-empty data, timestamp age, and planning-frame checks. +- [x] 3.3 Call `GraspGenSpec.propose_grasps`, validate the candidate-array header and poses, and preserve stable descending generator-score order. +- [x] 3.4 Derive pre-grasp and retreat targets from the configured TCP approach axis and candidate pose. +- [x] 3.5 Implement no-motion feasibility gating for pre-grasp, grasp, and retreat targets, capped by configuration and reporting rejection counts by reason. +- [x] 3.6 Retain the heuristic generator only behind explicit fallback configuration and identify the selected proposal source in results. +- [x] 3.7 Add unit tests for duplicate names, ID prefixes, missing/stale/wrong-frame clouds, provider failures, malformed candidates, stable score ties, candidate limits, and lower-ranked feasible selection. + +## 4. Pick Transaction and Verification + +- [x] 4.1 Add a single-active-pick guard that rejects concurrent transactions without mutating robot, gripper, proposal, or obstacle state. +- [x] 4.2 Implement the `PREPARE`, `APPROACH`, `GRASP`, `CLOSE`, `VERIFY`, and `RETREAT` phase runner behind the existing `pick` signature. +- [x] 4.3 Check every planning, execution, wait, and gripper-command result; regenerate motion plans from live state at each phase and terminate after the first post-motion failure. +- [x] 4.4 Replace fixed grasp sleeps with timeout-bounded gripper feedback polling and robot-specific held/empty threshold evaluation. +- [x] 4.5 Preserve a closed gripper on every post-closure failure, include “object may be held” context, and store `_last_pick_pose` only after verified closure and successful retreat. +- [x] 4.6 Guarantee transaction and target-suppression cleanup through one exit path while retaining the primary failure if cleanup also fails. +- [x] 4.7 Add phase-by-phase unit tests for success, command rejection, planning/execution failure, timeout, empty close, retreat failure, cleanup failure, and concurrent calls. + +## 5. Blueprint and Agent Integration + +- [x] 5.1 Define reviewed xArm sweep-volume, grasp-frame-to-TCP, approach-axis, and closure-verification configuration without performing model or hardware work at import time. +- [x] 5.2 Add a distinct GraspGenX-enabled xArm perception blueprint and agentic blueprint that compose exactly one perception provider, proposal provider, manipulation module, MCP server, and MCP client. +- [x] 5.3 Keep existing xArm perception blueprints free of the `graspgenx` runtime requirement and explicitly configure their intended heuristic fallback behavior. +- [x] 5.4 Update the manipulation agent prompt to keep `pick` as the sole high-level pick tool and document exact-name/object-ID disambiguation plus safe recovery. +- [x] 5.5 Regenerate `dimos/robot/all_blueprints.py` through `test_all_blueprints_generation.py` and verify the new names appear in `dimos list`. + +## 6. End-to-End Validation and Documentation + +- [x] 6.1 Add integration tests with fake perception, proposal, planner/coordinator, and gripper feedback providers that exercise the full RPC/Spec-wired pipeline. +- [x] 6.2 Add a replay or fixture-based test proving object/proposal/planning frame consistency with real `PointCloud2` and `GraspCandidateArray` messages. +- [ ] 6.3 Validate the GraspGenX-enabled blueprint startup and one successful/one infeasible candidate flow with the `graspgenx` extra on a GPU-capable environment. +- [ ] 6.4 Calibrate and record the xArm empty-close versus held-object threshold across representative object widths before enabling verification on hardware. +- [ ] 6.5 Run guarded real-xArm tests for success, empty grasp, unreachable proposals, execution interruption, and retreat failure; verify the gripper never auto-opens after closure. +- [x] 6.6 Update manipulation capability documentation with architecture, configuration, failure semantics, and the distinction between closure-proxy verification and force/slip verification. +- [x] 6.7 Run focused pytest suites, blueprint-generation validation, formatting/lint checks, and mypy on touched modules. diff --git a/pyproject.toml b/pyproject.toml index 37bc540ae5..65c61c708f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -365,6 +365,14 @@ scene = [ "usd-core>=23.11", ] +graspgenx = [ + "graspgenx", + "huggingface-hub>=0.30,<1", + "matplotlib>=3.7.1", + "torch>=2.1,<2.7", + "torchvision>=0.16,<0.22", +] + all = [ "dimos[agents,apriltag,base,cpu,cuda,drone,manipulation,misc,perception,scene,sim,unitree,visualization,web,webrtc]", ] @@ -506,8 +514,20 @@ override-dependencies = [ # uv picks importlib-metadata>=9 and cascades the otel stack down to 1.11.1, # whose stale _pb2.py files crash with protobuf>=6. "importlib-metadata<8.8.0", + # GraspGenX pins older versions that conflict with the current DimOS stack. + # These versions were validated with the pinned GraspGenX source revision. + "yourdfpy>=0.0.60", + "trimesh>=4.12", + "numpy>=2", + "timm>=1.0.17", + "huggingface-hub>=0.30,<1", + "diffusers>=0.29", + "pyopengl>=3.1.5", ] +[tool.uv.sources] +graspgenx = { git = "https://github.com/NVlabs/GraspGenX.git", rev = "b9429097728cb1c430dd78b92edf17ba318aad03" } + [tool.ruff] line-length = 100 exclude = [ diff --git a/uv.lock b/uv.lock index 2292f5837e..5d4fec44f7 100644 --- a/uv.lock +++ b/uv.lock @@ -39,11 +39,18 @@ roboplan = false [manifest] overrides = [ + { name = "diffusers", specifier = ">=0.29" }, + { name = "huggingface-hub", specifier = ">=0.30,<1" }, { name = "importlib-metadata", specifier = "<8.8.0" }, { name = "langgraph-prebuilt", specifier = "<=1.0.8" }, + { name = "numpy", specifier = ">=2" }, { name = "opencv-python", marker = "sys_platform == 'never'" }, { name = "pillow", specifier = ">=12.2.0" }, + { name = "pyopengl", specifier = ">=3.1.5" }, { name = "pytest", specifier = "==8.3.5" }, + { name = "timm", specifier = ">=1.0.17" }, + { name = "trimesh", specifier = ">=4.12" }, + { name = "yourdfpy", specifier = ">=0.0.60" }, ] [[package]] @@ -544,6 +551,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458, upload-time = "2024-11-08T17:25:46.184Z" }, ] +[[package]] +name = "braceexpand" +version = "0.1.7" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/54/93/badd4f5ccf25209f3fef2573073da9fe4a45a3da99fca2f800f942130c0f/braceexpand-0.1.7.tar.gz", hash = "sha256:e6e539bd20eaea53547472ff94f4fb5c3d3bf9d0a89388c4b56663aba765f705", size = 7777, upload-time = "2021-05-07T13:49:07.323Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/fa/93/e8c04e80e82391a6e51f218ca49720f64236bc824e92152a2633b74cf7ab/braceexpand-0.1.7-py2.py3-none-any.whl", hash = "sha256:91332d53de7828103dcae5773fb43bc34950b0c8160e35e0f44c4427a3b85014", size = 5923, upload-time = "2021-05-07T13:49:05.146Z" }, +] + [[package]] name = "brax" version = "0.14.1" @@ -1079,24 +1095,21 @@ wheels = [ ] [[package]] -name = "colorlog" -version = "6.9.0" +name = "comm" +version = "0.2.3" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d3/7a/359f4d5df2353f26172b3cc39ea32daa39af8de522205f512f458923e677/colorlog-6.9.0.tar.gz", hash = "sha256:bfba54a1b93b94f54e1f4fe48395725a3d92fd2a4af702f6bd70946bdc0c6ac2", size = 16624, upload-time = "2024-10-29T18:34:51.011Z" } +sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e3/51/9b208e85196941db2f0654ad0357ca6388ab3ed67efdbfc799f35d1f83aa/colorlog-6.9.0-py3-none-any.whl", hash = "sha256:5906e71acd67cb07a71e779c47c4bcb45fb8c2993eebe9e5adcd6a6f1b283eff", size = 11424, upload-time = "2024-10-29T18:34:49.815Z" }, + { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, ] [[package]] -name = "comm" -version = "0.2.3" +name = "config-path" +version = "1.0.5" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/4c/13/7d740c5849255756bc17888787313b61fd38a0a8304fc4f073dfc46122aa/comm-0.2.3.tar.gz", hash = "sha256:2dc8048c10962d55d7ad693be1e7045d891b7ce8d999c97963a5e3e99c055971", size = 6319, upload-time = "2025-07-25T14:02:04.452Z" } +sdist = { url = "https://files.pythonhosted.org/packages/32/02/75ee14911378497eb3bb1bfa576cd99bc88ecf0d9f60671721a91dbecaaf/config-path-1.0.5.tar.gz", hash = "sha256:ed17ad1a0cbdadb2781443a27a624057138611416885acb3fb41132a6d559d77", size = 11215, upload-time = "2023-10-03T19:41:43.639Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/60/97/891a0971e1e4a8c5d2b20bbe0e524dc04548d2307fee33cdeba148fd4fc7/comm-0.2.3-py3-none-any.whl", hash = "sha256:c615d91d75f7f04f095b30d1c1711babd43bdc6419c1be9886a85f2f4e489417", size = 7294, upload-time = "2025-07-25T14:02:02.896Z" }, + { url = "https://files.pythonhosted.org/packages/5f/51/eec3b34b71799668009107b48fa146b9b9d347f60749491ca77a85ccb94c/config_path-1.0.5-py3-none-any.whl", hash = "sha256:7a69fbfdacb95d7591621e67265a87960d54d56abffe95d9f93acd342f926292", size = 8800, upload-time = "2023-10-03T19:41:42.129Z" }, ] [[package]] @@ -1350,27 +1363,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4a/f4/d23dbfb9c62cb642c114a30f05d753ba61d6ffbfd8a3a4012fe85a073bcb/ctranslate2-4.7.1-cp312-cp312-win_amd64.whl", hash = "sha256:d0f734dc3757118094663bdaaf713f5090c55c1927fb330a76bb8b84173940e8", size = 18844949, upload-time = "2026-02-04T06:11:45.436Z" }, ] -[[package]] -name = "cuda-bindings" -version = "12.9.4" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "cuda-pathfinder", marker = "platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/7a/d8/b546104b8da3f562c1ff8ab36d130c8fe1dd6a045ced80b4f6ad74f7d4e1/cuda_bindings-12.9.4-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:4d3c842c2a4303b2a580fe955018e31aea30278be19795ae05226235268032e5", size = 12148218, upload-time = "2025-10-21T14:51:28.855Z" }, - { url = "https://files.pythonhosted.org/packages/45/e7/b47792cc2d01c7e1d37c32402182524774dadd2d26339bd224e0e913832e/cuda_bindings-12.9.4-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c912a3d9e6b6651853eed8eed96d6800d69c08e94052c292fec3f282c5a817c9", size = 12210593, upload-time = "2025-10-21T14:51:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/a9/c1/dabe88f52c3e3760d861401bb994df08f672ec893b8f7592dc91626adcf3/cuda_bindings-12.9.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:fda147a344e8eaeca0c6ff113d2851ffca8f7dfc0a6c932374ee5c47caa649c8", size = 12151019, upload-time = "2025-10-21T14:51:43.167Z" }, -] - -[[package]] -name = "cuda-pathfinder" -version = "1.3.4" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/b8/5e/db279a3bfbd18d59d0598922a3b3c1454908d0969e8372260afec9736376/cuda_pathfinder-1.3.4-py3-none-any.whl", hash = "sha256:fb983f6e0d43af27ef486e14d5989b5f904ef45cedf40538bfdcbffa6bb01fb2", size = 30878, upload-time = "2026-02-11T18:50:31.008Z" }, -] - [[package]] name = "cupy-cuda12x" version = "13.6.0" @@ -1439,6 +1431,36 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/07/ab/acaa119f552019bdb2b06478553cf712967672f5970be80ecc9b4ca805f4/cyclonedds-0.10.5-cp310-cp310-win_amd64.whl", hash = "sha256:103a681e9490229f12c151a125e00c4db8fdb344c8e12e35ee515cd9d5d1ecd7", size = 1200672, upload-time = "2024-06-05T18:50:54.303Z" }, ] +[[package]] +name = "cython" +version = "3.2.9" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/f6/de/db48b8870e766cfea809986cc50c1e986c663a9ab7bafd0ac1a2512c4a26/cython-3.2.9.tar.gz", hash = "sha256:d249c9022ab13286b17bd66f30609e800c5f95efeecb06168990c7a66cecde6c", size = 3293493, upload-time = "2026-07-24T06:21:21.867Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/27/29/bc7e088201af74eacbb5799542d0f09d6e5e139cc55f19fd54c409f58109/cython-3.2.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2751b7c0cb13135aadcc1cc8fe223be98d458cd1132611d31675e768a5e4a2e9", size = 3000461, upload-time = "2026-07-24T06:21:37.078Z" }, + { url = "https://files.pythonhosted.org/packages/81/e8/ac8266fa88b3a80009a2fa55b2729be1518c05d182888223f95b8e2a2b8e/cython-3.2.9-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ccb0bc6b8437cfcb04459a02bc5ecc58bf161ea11659438945ab5a1392ee39fe", size = 3306728, upload-time = "2026-07-24T06:21:39.011Z" }, + { url = "https://files.pythonhosted.org/packages/df/9b/b4ff8cdff357ba0f41791e044a2a95898d0768b20e9247823decf5eabb5e/cython-3.2.9-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1c2974742433be5c36ae1df1e761ce0063753fc2702316d5a3062b0a4e94a1df", size = 3458732, upload-time = "2026-07-24T06:21:41.034Z" }, + { url = "https://files.pythonhosted.org/packages/f3/eb/06ba035f8b845d2d7d67f533a58ebd885ceab1e48b8cc442f093ecf59201/cython-3.2.9-cp310-cp310-win_amd64.whl", hash = "sha256:a3fc783d12202d1b064b6f125772d85f00e36e62eee6b2e415f56d8fd2d2e7b2", size = 2785625, upload-time = "2026-07-24T06:21:42.969Z" }, + { url = "https://files.pythonhosted.org/packages/fc/97/2f477d30fc6ca0ff333552233aa6dee0e57323a55913f84f24da9cac26fc/cython-3.2.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e8ec2d5a7b798c84d6779e7ca5318fd928b8fe9dd021106d714dc2e77cdc7555", size = 2992696, upload-time = "2026-07-24T06:21:44.931Z" }, + { url = "https://files.pythonhosted.org/packages/0a/bf/0f280f7960c129957d2ce650e7fd7dec8e706c26d61a428e93927f4c9904/cython-3.2.9-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cac792b0bde1c86d8f832e513cd061b7e682181cc5a6d843d487e6c8ae9d5fcb", size = 3307656, upload-time = "2026-07-24T06:21:46.649Z" }, + { url = "https://files.pythonhosted.org/packages/ac/cd/6d7c35a1065f1a07c9fa5ec784ee562f32f36a8fd05748e5f10694887eb7/cython-3.2.9-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8c843dd85857cd0d0da055489f448d032866120cfb094ce16205a1ff54d06353", size = 3459259, upload-time = "2026-07-24T06:21:48.881Z" }, + { url = "https://files.pythonhosted.org/packages/9f/f6/c1ad54ec35fcd5c5a5808d8b5b6319d5ae96acc63f7b5c1f7812f8cc3f71/cython-3.2.9-cp311-cp311-win_amd64.whl", hash = "sha256:efd54fe07f808e7e82f6a04f370457ca02e770c948f0e2f83345ccc7ec8bb829", size = 2789027, upload-time = "2026-07-24T06:21:50.551Z" }, + { url = "https://files.pythonhosted.org/packages/fd/37/c74d842306c8fe381c415b37460d5e3086a820fac72b8ff5cb48513ccfcd/cython-3.2.9-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:114b2dee0fa1daa48a59574d848da0ff1b6bdb725a755e9b92fad14962e1ff8d", size = 3009571, upload-time = "2026-07-24T06:21:52.534Z" }, + { url = "https://files.pythonhosted.org/packages/15/c0/f7c42b161edd585e3ae556fd62a2c72cd80a6ed527a9907f0c5c6fb060de/cython-3.2.9-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a5cd9c5f138cb052130b40ad3b6976d2180c35348410995812678f4636bd8f94", size = 3183562, upload-time = "2026-07-24T06:21:54.62Z" }, + { url = "https://files.pythonhosted.org/packages/56/1b/c04520ac7f3157aa12a69b632c16261170dac9fab6c48608cc004b8f1b17/cython-3.2.9-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:23e80bc885c599e72072e18d0746df82d394b73100c1e153cda7359e6e59fe09", size = 3354811, upload-time = "2026-07-24T06:21:56.72Z" }, + { url = "https://files.pythonhosted.org/packages/0b/b5/4ce33235a25b19fcd51dc639f0f403b783a3b7f9b1934eade0d993fbe029/cython-3.2.9-cp312-cp312-win_amd64.whl", hash = "sha256:4b1fd5a9c03f72a18618668a8e90d569442ed742f910e3ad003dcc9348e9598b", size = 2778077, upload-time = "2026-07-24T06:21:58.7Z" }, + { url = "https://files.pythonhosted.org/packages/0a/4a/342312c5fe021c8e0c386e1915d138e0902c48ae179b0374ab04773a8831/cython-3.2.9-cp39-abi3-macosx_10_9_x86_64.whl", hash = "sha256:944dc8747f640b3527649c566a5fc75ee0c15e80642ea2fdae4fe6378e1a9d4a", size = 2899729, upload-time = "2026-07-24T06:22:24.877Z" }, + { url = "https://files.pythonhosted.org/packages/f0/62/ea919ee426cb4d435ec8155e1ee6bcbb46b20d8f070527191b59769d4e7f/cython-3.2.9-cp39-abi3-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4b871ad97dd7fb1cbf56f6238c54423febd310afc1d9d9bc70c69c89b7ce57fc", size = 3226650, upload-time = "2026-07-24T06:22:26.947Z" }, + { url = "https://files.pythonhosted.org/packages/18/02/057b4f63e2ced8c3cf217c4e9fb544bfe48145f493347c7ca3f51607526c/cython-3.2.9-cp39-abi3-manylinux2014_armv7l.manylinux_2_17_armv7l.manylinux_2_31_armv7l.whl", hash = "sha256:e9b6ebc6c74b4318eaa4e51e520dc8b95ebc7b262953c3ecb24131104681f14e", size = 2881919, upload-time = "2026-07-24T06:22:29.318Z" }, + { url = "https://files.pythonhosted.org/packages/64/e4/e158793ee3de7e4417ba17e7ff1015d6e2cf557cb485ad270b2446c9d1c7/cython-3.2.9-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:92989da161a7d18a7ad4baebc49289b2b77556d5a94916f90140ba26aecf6892", size = 3004702, upload-time = "2026-07-24T06:22:31.535Z" }, + { url = "https://files.pythonhosted.org/packages/f9/c0/d5bbbd743ab4feddb24a7e823b34c3ec4ebab91ff503d16743a4e7ce106b/cython-3.2.9-cp39-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:e75ec625d8f8781ced690b7a2f5c2d138067711cf24bb8fb68c872c30c2fefe5", size = 2902695, upload-time = "2026-07-24T06:22:33.525Z" }, + { url = "https://files.pythonhosted.org/packages/d6/2f/80850817395985259f135baa510d9186d3a325df81cd1862060bba977029/cython-3.2.9-cp39-abi3-musllinux_1_2_i686.whl", hash = "sha256:2b1756ddc3bc0cd4341a515fc420c3e25e13c249f5537159b3fb0bff8d19e55c", size = 3241554, upload-time = "2026-07-24T06:22:35.667Z" }, + { url = "https://files.pythonhosted.org/packages/4c/ce/6be776814f6cb81751f3da737ed537385738148e1ea99f89fb4637799198/cython-3.2.9-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:7d41baea51ea00f9237f75af498577827493bca5e9b45bbd4e351543727e589a", size = 3124337, upload-time = "2026-07-24T06:22:37.868Z" }, + { url = "https://files.pythonhosted.org/packages/e2/48/27c948cbbfe6050994e67a497ae530955dcda7e79084319321c49969e0fd/cython-3.2.9-cp39-abi3-win32.whl", hash = "sha256:61d4abbf84f77c8d19361d05d9f51d65d8d95e74f736eae55fa1aed8a1430469", size = 2435609, upload-time = "2026-07-24T06:22:40.005Z" }, + { url = "https://files.pythonhosted.org/packages/17/ef/cf0e1bd7542296f1752be63b027f90271448d8c8062eac66d8e44a79b883/cython-3.2.9-cp39-abi3-win_arm64.whl", hash = "sha256:57a6a78d14f7dd7d6062d9bca694e2a8c1c14113b6ceceea076abcd1161fdc5a", size = 2458025, upload-time = "2026-07-24T06:22:41.973Z" }, + { url = "https://files.pythonhosted.org/packages/00/ec/e61deec9bcfbb0e1b36f8b5ba75cb44644419b4bfd0fdd666bffd21d9579/cython-3.2.9-py3-none-any.whl", hash = "sha256:a2b0e87f6b80790c929308ca0831d686f7a180feab684fe8cd4a4380bd96aaca", size = 1259272, upload-time = "2026-07-24T06:21:18.95Z" }, +] + [[package]] name = "dash" version = "4.0.0" @@ -1522,6 +1544,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/4e/8c/f3147f5c4b73e7550fe5f9352eaa956ae838d5c51eb58e7a25b9f3e2643b/decorator-5.2.1-py3-none-any.whl", hash = "sha256:d316bb415a2d9e2d2b3abcc4084c6502fc09240e292cd76a76afc106a1c8e04a", size = 9190, upload-time = "2025-02-24T04:41:32.565Z" }, ] +[[package]] +name = "diffusers" +version = "0.39.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "httpx" }, + { name = "huggingface-hub" }, + { name = "importlib-metadata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pillow" }, + { name = "regex" }, + { name = "requests" }, + { name = "safetensors" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/1a/81/6095237b86a3116c4789f28c4435d5296c00c0fc74ffde99008fd6b3a36c/diffusers-0.39.0.tar.gz", hash = "sha256:14bb1d98c85a0e463d734c99aaa73b480a7bc9bad22af30fbf730ef8f09c1d67", size = 4651240, upload-time = "2026-07-03T08:48:47.904Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3f/3f/7469c46e9d22307ea686bab687d70e6bf328722952f9d10339f5e913e608/diffusers-0.39.0-py3-none-any.whl", hash = "sha256:912aca51b5787365110806e984d5555735bf8a461073bb8459029d0bca7870ef", size = 5631176, upload-time = "2026-07-03T08:48:45.337Z" }, +] + [[package]] name = "dill" version = "0.4.1" @@ -1700,7 +1743,7 @@ base = [ { name = "transformers", extra = ["torch"] }, { name = "ultralytics" }, { name = "uvicorn" }, - { name = "yourdfpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "yourdfpy" }, ] cpu = [ { name = "onnxruntime" }, @@ -1715,6 +1758,13 @@ dds = [ drone = [ { name = "pymavlink" }, ] +graspgenx = [ + { name = "graspgenx" }, + { name = "huggingface-hub" }, + { name = "matplotlib" }, + { name = "torch" }, + { name = "torchvision" }, +] learning = [ { name = "h5py" }, { name = "pandas", version = "2.3.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -1804,7 +1854,7 @@ unitree = [ { name = "ultralytics" }, { name = "unitree-webrtc-connect" }, { name = "uvicorn" }, - { name = "yourdfpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "yourdfpy" }, ] unitree-dds = [ { name = "aioquic" }, @@ -1839,12 +1889,12 @@ unitree-dds = [ { name = "unitree-sdk2py-dimos" }, { name = "unitree-webrtc-connect" }, { name = "uvicorn" }, - { name = "yourdfpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "yourdfpy" }, ] visualization = [ { name = "dimos-viewer" }, { name = "rerun-sdk" }, - { name = "yourdfpy", marker = "platform_machine != 'aarch64' or sys_platform != 'linux'" }, + { name = "yourdfpy" }, ] web = [ { name = "aioquic" }, @@ -2060,8 +2110,10 @@ requires-dist = [ { name = "filelock", specifier = ">=3.16,<4" }, { name = "gdown", marker = "extra == 'misc'", specifier = ">=5.2.2" }, { name = "googlemaps", marker = "extra == 'misc'", specifier = ">=4.10.0" }, + { name = "graspgenx", marker = "extra == 'graspgenx'", git = "https://github.com/NVlabs/GraspGenX.git?rev=b9429097728cb1c430dd78b92edf17ba318aad03" }, { name = "gtsam-extended", marker = "extra == 'mapping'", specifier = ">=4.3a1.post1" }, { name = "h5py", marker = "extra == 'learning'" }, + { name = "huggingface-hub", marker = "extra == 'graspgenx'", specifier = ">=0.30,<1" }, { name = "hydra-core", marker = "extra == 'perception'", specifier = ">=1.3.0" }, { name = "imagecodecs", specifier = ">=2024.6.1" }, { name = "ipykernel", marker = "extra == 'misc'" }, @@ -2077,6 +2129,7 @@ requires-dist = [ { name = "llvmlite", specifier = ">=0.42.0" }, { name = "lz4", specifier = ">=4.4.5" }, { name = "manifold3d", marker = "extra == 'apriltag'", specifier = ">=2.5.0" }, + { name = "matplotlib", marker = "extra == 'graspgenx'", specifier = ">=3.7.1" }, { name = "matplotlib", marker = "extra == 'manipulation'", specifier = ">=3.7.1" }, { name = "mcap", marker = "extra == 'unitree-dds'", specifier = ">=1.2.0" }, { name = "moondream", marker = "extra == 'perception'" }, @@ -2134,7 +2187,9 @@ requires-dist = [ { name = "textual-serve", specifier = ">=1.1.1,<2" }, { name = "timm", marker = "extra == 'misc'", specifier = ">=1.0.15" }, { name = "toolz", specifier = ">=1.1.0" }, + { name = "torch", marker = "extra == 'graspgenx'", specifier = ">=2.1,<2.7" }, { name = "torchreid", marker = "extra == 'misc'", specifier = "==0.2.5" }, + { name = "torchvision", marker = "extra == 'graspgenx'", specifier = ">=0.16,<0.22" }, { name = "transformers", extras = ["torch"], marker = "extra == 'perception'", specifier = ">=4.53.0,<4.54" }, { name = "trimesh", marker = "extra == 'apriltag'", specifier = ">=4.0.0" }, { name = "trimesh", marker = "extra == 'manipulation'" }, @@ -2154,7 +2209,7 @@ requires-dist = [ { name = "yourdfpy", marker = "(platform_machine != 'aarch64' and extra == 'visualization') or (sys_platform != 'linux' and extra == 'visualization')", specifier = ">=0.0.60" }, { name = "yourdfpy", marker = "extra == 'manipulation'", specifier = ">=0.0.60" }, ] -provides-extras = ["misc", "visualization", "learning", "agents", "web", "perception", "unitree", "unitree-dds", "manipulation", "cpu", "cuda", "sim", "mapping", "drone", "dds", "webrtc", "base", "apriltag", "scene", "all"] +provides-extras = ["misc", "visualization", "learning", "agents", "web", "perception", "unitree", "unitree-dds", "manipulation", "cpu", "cuda", "sim", "mapping", "drone", "dds", "webrtc", "base", "apriltag", "scene", "graspgenx", "all"] [package.metadata.requires-dev] autofix = [{ name = "ruff", specifier = "==0.14.3" }] @@ -2369,6 +2424,76 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/12/b3/231ffd4ab1fc9d679809f356cebee130ac7daa00d6d6f3206dd4fd137e9e/distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2", size = 20277, upload-time = "2023-12-24T09:54:30.421Z" }, ] +[[package]] +name = "dm-control" +version = "1.0.43" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "dm-env" }, + { name = "dm-tree" }, + { name = "glfw" }, + { name = "labmaze" }, + { name = "lxml" }, + { name = "mujoco" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "protobuf" }, + { name = "pyopengl" }, + { name = "pyparsing" }, + { name = "requests" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "setuptools" }, + { name = "tqdm" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/f0/eb/dcb0528623f58196522d74e7b2ec4a0cb5e890b9c5d28c7730de9e346d1a/dm_control-1.0.43.tar.gz", hash = "sha256:8f0e27246939cbafb3ca37d5a620056b77a95b5b8ba061b33a7aaca3d2185338", size = 56276100, upload-time = "2026-06-22T18:55:42.307Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/9f/50/4d9a4ea0ceb5c9399412834877fba79acb4438bac9c520a9cab57513a93b/dm_control-1.0.43-py3-none-any.whl", hash = "sha256:4d79532c44fb7660825b1c201d0172b14f0c8585a1f39f6cbc0181ee16e5f8e2", size = 56446782, upload-time = "2026-06-22T18:55:36.927Z" }, +] + +[[package]] +name = "dm-env" +version = "1.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "dm-tree" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/62/c9/93e8d6239d5806508a2ee4b370e67c6069943ca149f59f533923737a99b7/dm-env-1.6.tar.gz", hash = "sha256:a436eb1c654c39e0c986a516cee218bea7140b510fceff63f97eb4fcff3d93de", size = 20187, upload-time = "2022-12-21T00:25:29.306Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/08/7e/36d548040e61337bf9182637a589c44da407a47a923ee88aec7f0e89867c/dm_env-1.6-py3-none-any.whl", hash = "sha256:0eabb6759dd453b625e041032f7ae0c1e87d4eb61b6a96b9ca586483837abf29", size = 26339, upload-time = "2022-12-21T00:25:37.128Z" }, +] + +[[package]] +name = "dm-tree" +version = "0.1.10" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "attrs" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "wrapt" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/66/a3ec619d22b6baffa5ab853e8dc6ec9d0c837127948af59bb15b988d7312/dm_tree-0.1.10.tar.gz", hash = "sha256:22f37b599e01cc3402a17f79c257a802aebd8d326de05b54657650845956208a", size = 35748, upload-time = "2026-03-31T17:35:39.03Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/52/76/781bc1a8ce0f4be153755e36be547d7d36964fb6d265b9b29503ed3e0a0f/dm_tree-0.1.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b8b606661abcb5336e60ce4cd6f3bec794ec794f91d8de001c1ea451dd7a7411", size = 311543, upload-time = "2026-03-31T17:35:04.766Z" }, + { url = "https://files.pythonhosted.org/packages/7c/b9/2f6278c07728c60411d363aab1b5de53b75bb3bdf27032e871c240f3b4de/dm_tree-0.1.10-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0fcf11edc379723b8a17b76b6f63643e9280d55f23c37994805bf27282074192", size = 181202, upload-time = "2026-03-31T17:35:06.266Z" }, + { url = "https://files.pythonhosted.org/packages/6d/d6/2b88e4eb5e3e5ecf9d61afe55e463ce7c9e4d1dc6630b9f7038a62e548d1/dm_tree-0.1.10-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7644b24bb5c7810b601f4b28cf6e4b516da96713ebfd9e45585240318c6b9384", size = 183874, upload-time = "2026-03-31T17:35:07.746Z" }, + { url = "https://files.pythonhosted.org/packages/49/9f/6fea8ba8cb69136af0511868bc41dfb88d0b8c3a85497dbbf16271cd84a7/dm_tree-0.1.10-cp310-cp310-win_amd64.whl", hash = "sha256:d7f42b2148a1a3758230fbf93c06b9475e5d4bd62a4d19eacafa8210b03421ac", size = 110740, upload-time = "2026-03-31T17:35:09.045Z" }, + { url = "https://files.pythonhosted.org/packages/87/dc/b01d0f70cde99b306731216a98287ba5926a50f27222f2ada0b99ad0911f/dm_tree-0.1.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8218af7b99701bb8b03001c82961dc2cf81d7a734958206d2ea1ede8fbbe2b5f", size = 314603, upload-time = "2026-03-31T17:35:10.052Z" }, + { url = "https://files.pythonhosted.org/packages/40/72/3bafa58492862360113c1cccb26747c7863d417271e1572bacb3c281162f/dm_tree-0.1.10-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cacef6180fcfef30bab2cac5164e753e2f7a2e60e5da0feb81f2d318416f8d98", size = 182657, upload-time = "2026-03-31T17:35:11.462Z" }, + { url = "https://files.pythonhosted.org/packages/78/10/587a2cdc05995069aa63b659d884eb3e58a3c86a5b4a00acdb7a316bddf3/dm_tree-0.1.10-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f0e8907bb6809dc195be3af077e382126eaebe06c00f835d09ae26e36d2165ff", size = 185008, upload-time = "2026-03-31T17:35:12.838Z" }, + { url = "https://files.pythonhosted.org/packages/60/0e/08d938d84cbf791dde009b3d3a6637f27a0004235e700641a0ac038daac5/dm_tree-0.1.10-cp311-cp311-win_amd64.whl", hash = "sha256:a1c82dd4726a16ac6b6f7a77a5fb097ee396fd349ae301407eb5736f15b8fa16", size = 111472, upload-time = "2026-03-31T17:35:14.035Z" }, + { url = "https://files.pythonhosted.org/packages/34/a1/17e0d68eec978c483db4712b14d083ee01484381b29ea85edb2b20210bd0/dm_tree-0.1.10-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:94af18e4fd22ce69eccae89eeed8ed498b6b4cc4957f4ed10b4160e59f620e1d", size = 315976, upload-time = "2026-03-31T17:35:15.21Z" }, + { url = "https://files.pythonhosted.org/packages/cc/6f/ed603715fbc29c887a8985252e2cfe0d449497aea96bac51010159771617/dm_tree-0.1.10-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b442a0c1e9d0960e0314a2e4af81fd328a87921b6d6db6dc41bfa420536884d6", size = 184053, upload-time = "2026-03-31T17:35:16.512Z" }, + { url = "https://files.pythonhosted.org/packages/83/eb/1d55c679cee9a54e552480d308535753c72e2250cf720d7aa777bff2a4fe/dm_tree-0.1.10-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:012c2b376e88d3685c73a4b5c23be41fe933e14e380dcd90172971690b0e02d2", size = 186506, upload-time = "2026-03-31T17:35:17.593Z" }, + { url = "https://files.pythonhosted.org/packages/89/2d/adef6924f8dc7f1665eea4ce066387820c14a629d0e1005568892d56ea6a/dm_tree-0.1.10-cp312-cp312-win_amd64.whl", hash = "sha256:da8d5b8995bea1b6bb93f457e0dad5d16e6e2344a6488ced55320e7f3fd50f56", size = 112708, upload-time = "2026-03-31T17:35:18.699Z" }, +] + [[package]] name = "dnspython" version = "2.8.0" @@ -2512,29 +2637,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/2a/09/f8d8f8f31e4483c10a906437b4ce31bdf3d6d417b73fe33f1a8b59e34228/einops-0.8.2-py3-none-any.whl", hash = "sha256:54058201ac7087911181bfec4af6091bb59380360f069276601256a76af08193", size = 65638, upload-time = "2026-01-26T04:13:18.546Z" }, ] -[[package]] -name = "embreex" -version = "4.4.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -wheels = [ - { url = "https://files.pythonhosted.org/packages/29/89/692237d6a60f58f87f7bffdfe8c30008efd8811411e692f96d2cb1df0818/embreex-4.4.0-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:b685ed766ef86dbd302ae9e92ab20156007c31cbd2ea5465542b50eea99c6da5", size = 5098460, upload-time = "2026-04-22T19:46:30.276Z" }, - { url = "https://files.pythonhosted.org/packages/14/fe/cedef264696768248f8139c569e10796ffb76627383adb5a60e1eaf28a20/embreex-4.4.0-cp310-cp310-macosx_13_0_x86_64.whl", hash = "sha256:daf8b1848798523d7d71cc22f2610401ab02ec93ea063f9cbb90dcb9abda2ccf", size = 13215487, upload-time = "2026-04-22T19:46:32.769Z" }, - { url = "https://files.pythonhosted.org/packages/11/f5/460b7f79689ac5e6ceb3ec2a1194176a0a66d6c4e010dae68ba899a1c927/embreex-4.4.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f548cbebc550624ef08530ed9dcd147eeddcd181fd8f32cf3378800b39b21034", size = 14438524, upload-time = "2026-04-22T19:46:35.533Z" }, - { url = "https://files.pythonhosted.org/packages/9f/c2/aef3606d7ca2b4d2d18e57c8f65762b94d253e678a05c946649bb1913f5e/embreex-4.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:58921ef7ad488dbd514b3053e7c4a9fdcd2f7008426b3185b9f1bd394c608edd", size = 13119003, upload-time = "2026-04-22T19:46:38.442Z" }, - { url = "https://files.pythonhosted.org/packages/d8/e1/84b02da29deac092349b12fae21a3cbf4b64104ef78e0d0c1bca5d268112/embreex-4.4.0-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b2e69b777c0b7878e13ad8c31f4fecaddb5cd8633a5814c1ac11da2efe1065dc", size = 5098210, upload-time = "2026-04-22T19:46:41.149Z" }, - { url = "https://files.pythonhosted.org/packages/4c/0c/1bcdcb8cb09713d40c3cc6d303a4e456113df859dacb28a1b7af8a19a718/embreex-4.4.0-cp311-cp311-macosx_13_0_x86_64.whl", hash = "sha256:96a187753f578cb685051f8fd679dc7986f887fcb922bb81a9924f5b89e941d8", size = 13214875, upload-time = "2026-04-22T19:46:43.439Z" }, - { url = "https://files.pythonhosted.org/packages/7d/1d/bed6f27a57b89ad028c8ec5adf6f1877a1ef92d983d703abb7a70717f0d9/embreex-4.4.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8415d14c8956afec7eb05749f1e0bf396bb51efd566054ca169e10e4089e7bb7", size = 14474358, upload-time = "2026-04-22T19:46:46.056Z" }, - { url = "https://files.pythonhosted.org/packages/21/f4/c3515fde7bacab245673988398ef40928ce0b9fb54e2b51e90a4a4535479/embreex-4.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:bfa54fb71cbb3a41c2366cabd09bb2dbbc8700843872f2c8655a3215a73459a7", size = 13119132, upload-time = "2026-04-22T19:46:48.753Z" }, - { url = "https://files.pythonhosted.org/packages/f6/04/b3413ba4f1c17f2374cc39b5b86404221aedc632c8b6cdb484697eeffcd8/embreex-4.4.0-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:3bb261a25c21d50bc7f046401e7256e59e43a500b229fb2a00b6393c61e5293d", size = 5097518, upload-time = "2026-04-22T19:46:51.379Z" }, - { url = "https://files.pythonhosted.org/packages/6d/78/8cc0960cc0f2d60d581869a66c8013e1bf1c73bf5bf9609bd8aa79e0f721/embreex-4.4.0-cp312-cp312-macosx_13_0_x86_64.whl", hash = "sha256:8e93bce7cf905365117dea2d0726d19262c88a010044d00631db6bb7dc145612", size = 13214768, upload-time = "2026-04-22T19:46:54.088Z" }, - { url = "https://files.pythonhosted.org/packages/79/b0/05a5b4d49749602b12e13d1871f8e6d1fe6db806eda75f6f57bb4f1acf6f/embreex-4.4.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cb0872f5bc231f465b840b122847acbaf468ac48f49bfaff127c5347ec0db94f", size = 14529899, upload-time = "2026-04-22T19:46:56.824Z" }, - { url = "https://files.pythonhosted.org/packages/b5/96/625e035f3433071c91de07e66265a261be7bb708367f785000f93d7a992a/embreex-4.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:6c092ee1adf5c48b7430c7ae3902943863745e54b4aef4327ecb3473e0a299d7", size = 13119305, upload-time = "2026-04-22T19:46:59.329Z" }, -] - [[package]] name = "etils" version = "1.13.0" @@ -2819,6 +2921,7 @@ resolution-markers = [ dependencies = [ { name = "jax", version = "0.6.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "msgpack", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "optax", marker = "python_full_version < '3.11'" }, { name = "orbax-checkpoint", marker = "python_full_version < '3.11'" }, { name = "pyyaml", marker = "python_full_version < '3.11'" }, @@ -2915,6 +3018,20 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c4/73/3a3e6cb864ddf98800a9236ad497d32e5b50eb1682ac659f7d669d92faec/foxglove_websocket-0.1.4-py3-none-any.whl", hash = "sha256:772e24e2c98bdfc704df53f7177c8ff5bab0abc4dac59a91463aca16debdd83a", size = 14392, upload-time = "2025-07-14T20:26:26.899Z" }, ] +[[package]] +name = "freetype-py" +version = "2.5.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/d0/9c/61ba17f846b922c2d6d101cc886b0e8fb597c109cedfcb39b8c5d2304b54/freetype-py-2.5.1.zip", hash = "sha256:cfe2686a174d0dd3d71a9d8ee9bf6a2c23f5872385cf8ce9f24af83d076e2fbd", size = 851738, upload-time = "2024-08-29T18:32:26.37Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/38/a8/258dd138ebe60c79cd8cfaa6d021599208a33f0175a5e29b01f60c9ab2c7/freetype_py-2.5.1-py3-none-macosx_10_9_universal2.whl", hash = "sha256:d01ded2557694f06aa0413f3400c0c0b2b5ebcaabeef7aaf3d756be44f51e90b", size = 1747885, upload-time = "2024-08-29T18:32:17.604Z" }, + { url = "https://files.pythonhosted.org/packages/a2/93/280ad06dc944e40789b0a641492321a2792db82edda485369cbc59d14366/freetype_py-2.5.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d2f6b3d68496797da23204b3b9c4e77e67559c80390fc0dc8b3f454ae1cd819", size = 1051055, upload-time = "2024-08-29T18:32:19.153Z" }, + { url = "https://files.pythonhosted.org/packages/b6/36/853cad240ec63e21a37a512ee19c896b655ce1772d803a3dd80fccfe63fe/freetype_py-2.5.1-py3-none-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:289b443547e03a4f85302e3ac91376838e0d11636050166662a4f75e3087ed0b", size = 1043856, upload-time = "2024-08-29T18:32:20.565Z" }, + { url = "https://files.pythonhosted.org/packages/93/6f/fcc1789e42b8c6617c3112196d68e87bfe7d957d80812d3c24d639782dcb/freetype_py-2.5.1-py3-none-musllinux_1_1_aarch64.whl", hash = "sha256:cd3bfdbb7e1a84818cfbc8025fca3096f4f2afcd5d4641184bf0a3a2e6f97bbf", size = 1108180, upload-time = "2024-08-29T18:32:21.871Z" }, + { url = "https://files.pythonhosted.org/packages/2a/1b/161d3a6244b8a820aef188e4397a750d4a8196316809576d015f26594296/freetype_py-2.5.1-py3-none-musllinux_1_1_x86_64.whl", hash = "sha256:3c1aefc4f0d5b7425f014daccc5fdc7c6f914fb7d6a695cc684f1c09cd8c1660", size = 1106792, upload-time = "2024-08-29T18:32:23.134Z" }, + { url = "https://files.pythonhosted.org/packages/93/6e/bd7fbfacca077bc6f34f1a1109800a2c41ab50f4704d3a0507ba41009915/freetype_py-2.5.1-py3-none-win_amd64.whl", hash = "sha256:0b7f8e0342779f65ca13ef8bc103938366fecade23e6bb37cb671c2b8ad7f124", size = 814608, upload-time = "2024-08-29T18:32:24.648Z" }, +] + [[package]] name = "frozenlist" version = "1.8.0" @@ -3096,6 +3213,48 @@ dependencies = [ ] sdist = { url = "https://files.pythonhosted.org/packages/fe/26/bca4d737a9acea25e94c19940a780bbf0be64a691f7caf3a68467d3a5838/googlemaps-4.10.0.tar.gz", hash = "sha256:3055fcbb1aa262a9159b589b5e6af762b10e80634ae11c59495bd44867e47d88", size = 33056, upload-time = "2023-01-26T16:45:02.501Z" } +[[package]] +name = "graspgenx" +version = "1.0.0" +source = { git = "https://github.com/NVlabs/GraspGenX.git?rev=b9429097728cb1c430dd78b92edf17ba318aad03#b9429097728cb1c430dd78b92edf17ba318aad03" } +dependencies = [ + { name = "addict" }, + { name = "diffusers" }, + { name = "h5py" }, + { name = "huggingface-hub" }, + { name = "hydra-core" }, + { name = "imageio" }, + { name = "matplotlib" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyopengl" }, + { name = "pyrender" }, + { name = "pytest" }, + { name = "pyyaml" }, + { name = "scene-synthesizer", extra = ["recommend"] }, + { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "setuptools" }, + { name = "sharedarray" }, + { name = "tensorboard" }, + { name = "tensorboardx" }, + { name = "tensordict" }, + { name = "timm" }, + { name = "torch" }, + { name = "torch-geometric" }, + { name = "torchvision" }, + { name = "tqdm" }, + { name = "transformers" }, + { name = "trimesh" }, + { name = "urdfpy" }, + { name = "viser" }, + { name = "webdataset" }, + { name = "yapf" }, + { name = "yourdfpy" }, +] + [[package]] name = "greenlet" version = "3.5.3" @@ -4049,6 +4208,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/46/2c/5c160dbdef7123f8cc97fd8ece7e0198627a426a2a49614845e9086feb8d/kubernetes-36.0.2-py2.py3-none-any.whl", hash = "sha256:faf9b5241b58de0c4a5069f2a0ffc8ac06fece7215156cd3d3ba081a78a858b6", size = 4617568, upload-time = "2026-06-01T18:20:28.737Z" }, ] +[[package]] +name = "labmaze" +version = "1.0.6" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "absl-py" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "setuptools" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/93/0a/139c4ae896b9413bd4ca69c62b08ee98dcfc78a9cbfdb7cadd0dce2ad31d/labmaze-1.0.6.tar.gz", hash = "sha256:2e8de7094042a77d6972f1965cf5c9e8f971f1b34d225752f343190a825ebe73", size = 4670455, upload-time = "2022-12-05T18:42:43.566Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/a0/0c/6a3941f48644c0b9305c7a22bd51974be1fed8e9233b16c893d728805143/labmaze-1.0.6-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:b2ddef976dfd8d992b19cfa6c633f2eba7576d759c2082da534e3f727479a84a", size = 4815423, upload-time = "2022-12-05T18:41:47.351Z" }, + { url = "https://files.pythonhosted.org/packages/d0/fe/b038c6a15732eb064767dc92ca39a38b2f5df183576384f0cfb6a4840f69/labmaze-1.0.6-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:157efaa93228c8ccce5cae337902dd652093e0fba9d3a0f6506e4bee272bb66f", size = 4806825, upload-time = "2022-12-05T18:41:49.922Z" }, + { url = "https://files.pythonhosted.org/packages/59/ec/2762281d4f26845b20bb7529742a6914fcb07c8e7c522175b879df0127cf/labmaze-1.0.6-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b3ce98b9541c5fe6a306e411e7d018121dd646f2c9978d763fad86f9f30c5f57", size = 4871532, upload-time = "2022-12-05T18:41:52.784Z" }, + { url = "https://files.pythonhosted.org/packages/4d/93/abac7877e1d7de984a2f0f5be561ff0dc795ae7e22595cf2f7c7032cd27e/labmaze-1.0.6-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4e6433bd49bc541791de8191040526fddfebb77151620eb04203453f43ee486a", size = 4875892, upload-time = "2022-12-05T18:41:55.603Z" }, + { url = "https://files.pythonhosted.org/packages/c4/10/5262db11b3c1db8e4fbc3feed9baed4f95db6047b8d9dcaf4f9fb8da9ba3/labmaze-1.0.6-cp310-cp310-win_amd64.whl", hash = "sha256:6a507fc35961f1b1479708e2716f65e0d0611cefb55f31a77be29ce2339b6fef", size = 4812953, upload-time = "2022-12-05T18:41:58.098Z" }, + { url = "https://files.pythonhosted.org/packages/6d/3c/cdc95db2aa8cd80c193b7b30b9a9be071897c4f0b558d5fc007b1adf74c3/labmaze-1.0.6-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:a0c2cb9dec971814ea9c5d7150af15fa3964482131fa969e0afb94bd224348af", size = 4815406, upload-time = "2022-12-05T18:42:00.412Z" }, + { url = "https://files.pythonhosted.org/packages/75/46/eb96e23ccddd40f403cea3f9f5d15eae7759317a1762b761692541edd6d9/labmaze-1.0.6-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2c6ba9538d819543f4be448d36b4926a3881e53646a2b331ebb5a1f353047d05", size = 4806777, upload-time = "2022-12-05T18:42:02.345Z" }, + { url = "https://files.pythonhosted.org/packages/0d/7e/787e0d3c17e29a46484158460e21fcf5cd7a076c81b2ec31807f2753ea43/labmaze-1.0.6-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:70635d1cdb0147a02efb6b3f607a52cdc51723bc3dcc42717a0d4ef55fa0a987", size = 4871563, upload-time = "2022-12-05T18:42:04.538Z" }, + { url = "https://files.pythonhosted.org/packages/a7/ce/be3952d7036b009f6dd004b6f5dfe97bbff79572ef0cf56a734aaead030f/labmaze-1.0.6-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff472793238bd9b6dabea8094594d6074ad3c111455de3afcae72f6c40c6817e", size = 4875913, upload-time = "2022-12-05T18:42:06.969Z" }, + { url = "https://files.pythonhosted.org/packages/50/a5/8c9f9be038401a31f9f87bd44f28c8edff63c0c3f1168ca882e351215761/labmaze-1.0.6-cp311-cp311-win_amd64.whl", hash = "sha256:2317e65e12fa3d1abecda7e0488dab15456cee8a2e717a586bfc8f02a91579e7", size = 4813089, upload-time = "2022-12-05T18:42:09.481Z" }, + { url = "https://files.pythonhosted.org/packages/cf/12/670a6e6beeeb166aa911fe861c1a16f62a9f3cfc7b54ea4b114cc23d0380/labmaze-1.0.6-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:e36b6fadcd78f22057b597c1c77823e806a0987b3bdfbf850e14b6b5b502075e", size = 4814941, upload-time = "2023-10-04T16:54:25.613Z" }, + { url = "https://files.pythonhosted.org/packages/e5/3a/47a3f83736e0b70f78b22d53e0a3230160a61e8ba6267003f25d2b24b832/labmaze-1.0.6-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d1a4f8de29c2c3d7f14163759b69cd3f237093b85334c983619c1db5403a223b", size = 4807545, upload-time = "2023-10-04T16:56:00.113Z" }, + { url = "https://files.pythonhosted.org/packages/ad/95/2ca4dd1efff4456f44baf4c4a980cfea6f6fb8729912a760ec9bf912876b/labmaze-1.0.6-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a394f8bb857fcaa2884b809d63e750841c2662a106cfe8c045f2112d201ac7d5", size = 4873133, upload-time = "2023-10-04T17:32:24.246Z" }, + { url = "https://files.pythonhosted.org/packages/f9/9c/1c928d0f5a20e4b9544d564e43ecda785f09a29ecbaa37f4e70989d0d4bd/labmaze-1.0.6-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0d17abb69d4dfc56183afb5c317e8b2eaca0587abb3aabd2326efd3143c81f4e", size = 4875122, upload-time = "2023-10-04T17:08:11.069Z" }, + { url = "https://files.pythonhosted.org/packages/5b/0f/13f0d54305e66c14c90512f3682f713273ec9aa94d107be7947157b37a74/labmaze-1.0.6-cp312-cp312-win_amd64.whl", hash = "sha256:5af997598cc46b1929d1c5a1febc32fd56c75874fe481a2a5982c65cee8450c9", size = 4811813, upload-time = "2023-10-04T17:20:30.837Z" }, +] + [[package]] name = "langchain" version = "1.2.3" @@ -4546,44 +4734,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/26/91/c4ef7c6d28f9fa71cbf0ad5fcafa6d706744065df7aa6b17256f009fb6cc/manifold3d-3.5.1-cp312-cp312-win_amd64.whl", hash = "sha256:08050488f36e59b39aef320d88dc32d63cdec016824dd3835ef835a2b74580ed", size = 1038017, upload-time = "2026-06-04T14:15:28.875Z" }, ] -[[package]] -name = "mapbox-earcut" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/bc/7b/bbf6b00488662be5d2eb7a188222c264b6f713bac10dc4a77bf37a4cb4b6/mapbox_earcut-2.0.0.tar.gz", hash = "sha256:81eab6b86cf99551deb698b98e3f7502c57900e5c479df15e1bdaf1a57f0f9d6", size = 39934, upload-time = "2025-11-16T18:41:27.251Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/d3/5222339a8fad091bf64f2e3041e48606d69d69f0609a7632ca17a8a05d5a/mapbox_earcut-2.0.0-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:c9a1dab7529f8e54bdb377f908e56f1e2b9a7e27ed168c64d3c7c38ed04ac201", size = 55920, upload-time = "2025-11-16T18:40:09.254Z" }, - { url = "https://files.pythonhosted.org/packages/19/e4/88d06e83ab75db2f4ae140a1e03ad8f84b02ac8af585dd61108aba73b8ed/mapbox_earcut-2.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5e5953098ea198253c8a40e2f282ca5b04d50ec2b9661e20c4cd2b2be39f0bb0", size = 52557, upload-time = "2025-11-16T18:40:10.536Z" }, - { url = "https://files.pythonhosted.org/packages/22/88/abefd244ea049e42334c5f7a9e3b58f4ec3c84d063119ba3c8d27ff31932/mapbox_earcut-2.0.0-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:efe5fd5de409e3b6d13907e73f295c8f1d63bdb6b8ca155dde4c93865796eafe", size = 56950, upload-time = "2025-11-16T18:40:11.905Z" }, - { url = "https://files.pythonhosted.org/packages/3c/e2/11122fddd086b930502eb4a954735da0f75e9d658fdab2d9e5914b9ebd2a/mapbox_earcut-2.0.0-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cd04da6edbca1dd68ddbfac2398a95c763f35d7317fed227fde5b3aff1253b18", size = 59618, upload-time = "2025-11-16T18:40:13.017Z" }, - { url = "https://files.pythonhosted.org/packages/e8/fd/e62195729daa3111fe95404a99c7a6b3aa174800373d10111b7e7278a789/mapbox_earcut-2.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8bdb8881e857d6d9277df696e9cfb8749c00d6162021d9359cba9da58dfdd4f5", size = 153021, upload-time = "2025-11-16T18:40:14.294Z" }, - { url = "https://files.pythonhosted.org/packages/2c/6a/d39ebaaa9010ea6c9f4d468f8812b1a1b31a40fba4f02ff29bc1bf321c30/mapbox_earcut-2.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:6e2d1bf5af90d5857955775b4d8ea15b02e172f2a8f194bba50ff95f8ff3e80e", size = 157736, upload-time = "2025-11-16T18:40:16.344Z" }, - { url = "https://files.pythonhosted.org/packages/20/00/6a59cdb8d8c1bf7e3cc92f0404f68fdb1a3cb0bbb0837af0dbb93d6290a6/mapbox_earcut-2.0.0-cp310-cp310-win32.whl", hash = "sha256:5b0aa63dd890d712343095b05eb7b60e071912ad3ced1fc4187d6a6a739677bc", size = 51564, upload-time = "2025-11-16T18:40:17.852Z" }, - { url = "https://files.pythonhosted.org/packages/bc/7b/af69669c959d8f7fd1bd49c15deace2360bf6a79dad7bf9f7a7f1c137da6/mapbox_earcut-2.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:b1355f13af89ea815b32f59a5455db295c965d51ab501bde0459cddc010a7149", size = 56793, upload-time = "2025-11-16T18:40:18.953Z" }, - { url = "https://files.pythonhosted.org/packages/07/9f/fbd15d9e348e75e986d6912c4eab99888106b7e5fb0a01e765422f7cd464/mapbox_earcut-2.0.0-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:9b5040e79e3783295e99c90277f31c1cbaddd3335297275331995ba5680e3649", size = 55773, upload-time = "2025-11-16T18:40:20.045Z" }, - { url = "https://files.pythonhosted.org/packages/72/40/be761298704fbbaa81c5618bb306f1510fb068e482f6a1c8b3b6c1b31479/mapbox_earcut-2.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1cf43baafec3ef1e967319d9b5da96bc6ddf3dbb204b6f3535275eda4b519a72", size = 52444, upload-time = "2025-11-16T18:40:21.501Z" }, - { url = "https://files.pythonhosted.org/packages/5a/0b/0c0c08db9663238ffb82c48259582dc0047a3255d98c0ac83c48026b7544/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:3a283531847f603dd9d69afb75b21bd009d385ca9485fcd3e5a7fa5db1ccd913", size = 56803, upload-time = "2025-11-16T18:40:22.891Z" }, - { url = "https://files.pythonhosted.org/packages/f0/4a/86796859383d7d11fa5d4bcf1983f94c6cbb9eeb60fb3bab527fec4b32fa/mapbox_earcut-2.0.0-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ab697676f4cec4572d4e941b7a3429a6687bf2ac6e8db3f3781024e3239ae3a0", size = 59403, upload-time = "2025-11-16T18:40:24.021Z" }, - { url = "https://files.pythonhosted.org/packages/6c/db/adaf981ab3bcfcf993ef317636b1f27210d6834bb1e8d63db6ad7c08214a/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:f1bdac76e048f4299accf4eaf797079ddfc330442e7231c15535ed198100d6c5", size = 152876, upload-time = "2025-11-16T18:40:25.588Z" }, - { url = "https://files.pythonhosted.org/packages/d2/83/86417974039e7554c9e1e55c852a7e9c2a1390d64675eb85d70e5fa7eb37/mapbox_earcut-2.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4a6945b23f859bef11ce3194303d17bd371c86b637e7029f81b1feaff3db3758", size = 157548, upload-time = "2025-11-16T18:40:27.202Z" }, - { url = "https://files.pythonhosted.org/packages/aa/4c/c82a292bb21e5c651d81334123db2d654c5c9d19b2197080d3429dc1e49a/mapbox_earcut-2.0.0-cp311-cp311-win32.whl", hash = "sha256:8e119524c29406afb5eaa15e933f297d35679293a3ca62ced22f97a14c484cb5", size = 51424, upload-time = "2025-11-16T18:40:28.415Z" }, - { url = "https://files.pythonhosted.org/packages/30/57/6c39d7db81f72a3e4814ef152c8fb8dfe275dc4b03c9bfa073d251e3755f/mapbox_earcut-2.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:378bbbb3304e446023752db8f44ecd6e7ef965bcbda36541d2ae64442ba94254", size = 56662, upload-time = "2025-11-16T18:40:29.863Z" }, - { url = "https://files.pythonhosted.org/packages/f4/d6/a1ef6e196b3d6968bf6546d4f7e54c559f9cff8991fdb880df0ba1618f52/mapbox_earcut-2.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:6d249a431abd6bbff36f1fd0493247a86de962244cc4081b4d5050b02ed48fb1", size = 50505, upload-time = "2025-11-16T18:40:30.992Z" }, - { url = "https://files.pythonhosted.org/packages/8d/93/846804029d955c3c841d8efff77c2b0e8d9aab057d3a077dc8e3f88b5ea4/mapbox_earcut-2.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:db55ce18e698bc9d90914ee7d4f8c3e4d23827456ece7c5d7a1ec91e90c7122b", size = 55623, upload-time = "2025-11-16T18:40:32.113Z" }, - { url = "https://files.pythonhosted.org/packages/d3/f6/cc9ece104bc3876b350dba6fef7f34fb7b20ecc028d2cdbdbecb436b1ed1/mapbox_earcut-2.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:01dd6099d16123baf582a11b2bd1d59ce848498cf0cdca3812fd1f8b20ff33b7", size = 52028, upload-time = "2025-11-16T18:40:33.516Z" }, - { url = "https://files.pythonhosted.org/packages/88/6e/230da4aabcc56c99e9bddb4c43ce7d4ba3609c0caf2d316fb26535d7c60c/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2d5a098aae26a52282bc981a38e7bf6b889d2ea7442f2cd1903d2ba842f4ff07", size = 56351, upload-time = "2025-11-16T18:40:35.217Z" }, - { url = "https://files.pythonhosted.org/packages/1a/f7/5cdd3752526e91d91336c7263af7767b291d21e63c89d7190a60051f0f87/mapbox_earcut-2.0.0-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:de35f241d0b9110ad9260f295acedd9d7cc0d7acfe30d36b1b3ee8419c2caba1", size = 59209, upload-time = "2025-11-16T18:40:36.634Z" }, - { url = "https://files.pythonhosted.org/packages/7b/a2/b7781416cb93b37b95d0444e03f87184de8815e57ff202ce4105fa921325/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6cb63ab85e2e430c350f93e75c13f8b91cb8c8a045f3cd714c390b69a720368a", size = 152316, upload-time = "2025-11-16T18:40:38.147Z" }, - { url = "https://files.pythonhosted.org/packages/c1/74/396338e3d345e4e36fb23a0380921098b6a95ce7fb19c4777f4185a5974e/mapbox_earcut-2.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fb3c9f069fc3795306db87f8139f70c4f047532f897a3de05f54dc1faebc97f6", size = 157268, upload-time = "2025-11-16T18:40:39.753Z" }, - { url = "https://files.pythonhosted.org/packages/56/2c/66fd137ea86c508f6cd7247f7f6e2d1dabffc9f0e9ccf14c71406b197af1/mapbox_earcut-2.0.0-cp312-cp312-win32.whl", hash = "sha256:eb290e6676217707ed238dd55e07b0a8ca3ab928f6a27c4afefb2ff3af08d7cb", size = 51226, upload-time = "2025-11-16T18:40:41.018Z" }, - { url = "https://files.pythonhosted.org/packages/b8/84/7b78e37b0c2109243c0dad7d9ba9774b02fcee228bf61cf727a5aa1702e2/mapbox_earcut-2.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:5ef5b3319a43375272ad2cad9333ed16e569b5102e32a4241451358897e6f6ee", size = 56417, upload-time = "2025-11-16T18:40:42.173Z" }, - { url = "https://files.pythonhosted.org/packages/75/7f/cd7195aa27c1c8f2b9d38025a5a8663f32cd01c07b648a54b1308ab26c15/mapbox_earcut-2.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:a4a3706feb5cc8c782d8f68bb0110c8d551304043f680a87a54b0651a2c208c3", size = 50111, upload-time = "2025-11-16T18:40:43.334Z" }, -] - [[package]] name = "markdown" version = "3.10.2" @@ -5050,7 +5200,7 @@ wheels = [ [[package]] name = "mujoco" -version = "3.5.0" +version = "3.10.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "absl-py" }, @@ -5060,23 +5210,23 @@ dependencies = [ { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "pyopengl" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/56/0d/005f0d49ad5878f0611a7c018550b8504d480a7a17ad7e6773ff47d8627a/mujoco-3.5.0.tar.gz", hash = "sha256:5c85a6fc7560ab5fa4534f35ff459e12dc3609681f307e457dbb49b6217f4d73", size = 912543, upload-time = "2026-02-13T01:02:51.554Z" } +sdist = { url = "https://files.pythonhosted.org/packages/a2/3b/c76837b7fdb007f7605ff783689a0bd23a5a49b065928bec2f1fa7ea3d67/mujoco-3.10.0.tar.gz", hash = "sha256:c9e8d5d87d82204ed5bccc87d843c0a53e75aaf381de2938ec46d04f1ac6e24e", size = 1094987, upload-time = "2026-06-22T17:40:59.904Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e8/20/9e0595e653543df3e4233bc3ad7e50b371b81dbe48d45ffbc867ed7c379d/mujoco-3.5.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:c4324161cb4f334dd984fbb4a4f7d7db9f914f40d06174b02dcf05463d8275e4", size = 7088320, upload-time = "2026-02-13T01:02:06.745Z" }, - { url = "https://files.pythonhosted.org/packages/8d/6b/fdac8ed97086e12ac930fb44e419eda1626e339010df73678cb1f22527d7/mujoco-3.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5f3803ff0dd7bc04d6c47d53a794343843bde06f0aeefeac28bb62b4cf2baab3", size = 7093261, upload-time = "2026-02-13T01:02:09.857Z" }, - { url = "https://files.pythonhosted.org/packages/19/ce/abcd9cc6ee7802f97c729ae0ccd517c68f04882f5db755b178e199511dc2/mujoco-3.5.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e13560991c779a139b53151733a0a6f3420ef09459b32d90302c2661c1b20992", size = 6637850, upload-time = "2026-02-13T01:02:11.808Z" }, - { url = "https://files.pythonhosted.org/packages/ca/d6/a5a7b615b257867b7c97db6b3ce07dec9351d5d9d5a5aca881cbb583d7a3/mujoco-3.5.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:01b12896ae906f157e18d8b1b7c24a8b72d2576fffa09869047150f186e92b33", size = 7079429, upload-time = "2026-02-13T01:02:13.738Z" }, - { url = "https://files.pythonhosted.org/packages/7e/91/d82dd3c16892e1b0e27a2f537eec8aad54d91d939cb3cd37db2e8c09ecc2/mujoco-3.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:2328358d2f0031175897092560dd6d04b14bab1cc22caa145ce99b843c17daa2", size = 5624454, upload-time = "2026-02-13T01:02:15.714Z" }, - { url = "https://files.pythonhosted.org/packages/8b/47/e923589301c197c3ea0776b60cc0d57383b3cc51639ca75e4e4b6c5334d6/mujoco-3.5.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:6b3ae97c3f84d093e84dc445a093c893d9f4b6f6bbb1a441e56d77074c450553", size = 7100854, upload-time = "2026-02-13T01:02:17.649Z" }, - { url = "https://files.pythonhosted.org/packages/82/02/aa6057ac4c50fb36558208005d6da19518f9a7857ef9b5fd2ed8f9262fe2/mujoco-3.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4fbb00809de98e8a65f2002745c5bca39076f8118b0fe08e973e7a99603c92b", size = 7105779, upload-time = "2026-02-13T01:02:19.621Z" }, - { url = "https://files.pythonhosted.org/packages/94/8a/8d87db2cf09a95ff4dcac1bd8eb6ccb95680804eff8f2f70f1d7a11e1980/mujoco-3.5.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:2a8d48990172d3b1eb51f20cd08f537c488686b2bc370c504333c07c04595f5d", size = 6651006, upload-time = "2026-02-13T01:02:22.197Z" }, - { url = "https://files.pythonhosted.org/packages/47/14/d5bf98385354318ec2e6c466a8c7cf7fd76f8b711ed6d11d155e2baa81fb/mujoco-3.5.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba54826121c6857fc4ca82df642d9a89174ce5537677c6ead34844bb692437e3", size = 7094833, upload-time = "2026-02-13T01:02:24.517Z" }, - { url = "https://files.pythonhosted.org/packages/b8/98/c1fac334cc764068e6c5d7eb01d6ed2a3392bab51952c816888b2dfe78c2/mujoco-3.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:ec0e35678773b34ee8b15741c34a745e027db062efcae790315aa83a5581c505", size = 5649612, upload-time = "2026-02-13T01:02:26.45Z" }, - { url = "https://files.pythonhosted.org/packages/f9/f0/4772421643f1c5aaf46d9e500a8716f59b02c8bf30bfa92cb8a763159efb/mujoco-3.5.0-cp312-cp312-macosx_10_16_x86_64.whl", hash = "sha256:ec0587cc423385a8d45343a981df58511cb69758ba99164a71567af2d41be3c9", size = 7100581, upload-time = "2026-02-13T01:02:29.182Z" }, - { url = "https://files.pythonhosted.org/packages/e1/d4/d0032323f58a9b8080b8464c6aade8d5ac2e101dbed1de64a38b3913b446/mujoco-3.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:94cf4285b46bc2d74fbe86e39a93ecfb3b0e584477fff7e38d293d47b88576e7", size = 7046132, upload-time = "2026-02-13T01:02:31.606Z" }, - { url = "https://files.pythonhosted.org/packages/b8/7b/c1612ec68d98e5f3dbc5b8a21ff5d40ab52409fcc89ea7afc8a197983297/mujoco-3.5.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:12bfb2bb70f760e0d51fd59f3c43b2906c7660a23954fd717321da52ba85a617", size = 6677917, upload-time = "2026-02-13T01:02:34.13Z" }, - { url = "https://files.pythonhosted.org/packages/c8/8a/229e4db3692be55532e155e2ca6a1363752243ee79df0e7e22ba00f716cf/mujoco-3.5.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:66fe37276644c28fab497929c55580725de81afc6d511a40cc27525a8dd99efa", size = 7170882, upload-time = "2026-02-13T01:02:36.086Z" }, - { url = "https://files.pythonhosted.org/packages/02/37/527d83610b878f27c01dd762e0e41aaa62f095c607f0500ac7f724a2c7a5/mujoco-3.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:4b3a62af174ab59b9b6d816dca0786b7fd85ac081d6c2a931a2b22dd6e821f50", size = 5721886, upload-time = "2026-02-13T01:02:39.544Z" }, + { url = "https://files.pythonhosted.org/packages/57/ed/9b8df6c801fa25542d4c2dc417063611474a070c4bef52e896fa57726d94/mujoco-3.10.0-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:c1c9dfb4ba3f1ef14b70968e9cd41b14fa1877f9697369953a471aa17324f443", size = 7745281, upload-time = "2026-06-22T17:39:47.198Z" }, + { url = "https://files.pythonhosted.org/packages/09/be/3d9a1ecfe3501a84ece0d5824ff67a0933337650fb48b26c8db0b43de0cd/mujoco-3.10.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c8e4688d87b85be27dfcfdf957f05f1450a99131f9f8b1818613a2e4fd8d7321", size = 19324219, upload-time = "2026-06-22T17:39:49.666Z" }, + { url = "https://files.pythonhosted.org/packages/03/37/5580b126403510a80a059a66d3ea21f3e55d47960e480e66ff2a7723b514/mujoco-3.10.0-cp310-cp310-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b4e1e2c0c72200340d413da4211b56a8885a7ed78e893f36d97e5696e8f4af3e", size = 19628590, upload-time = "2026-06-22T17:39:53.624Z" }, + { url = "https://files.pythonhosted.org/packages/d6/3c/e3768418794c4450c6bef971eaa2314e1fac7d46abc6968b6722b0b654a0/mujoco-3.10.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47a89c371cc171a38eb6c03172aff2f905e54c1261d87b3575a9d77fe3a29a55", size = 20763444, upload-time = "2026-06-22T17:39:56.559Z" }, + { url = "https://files.pythonhosted.org/packages/af/3c/a3c5121ca9356e78dd1f09ad48e7fa034b91e02124d533e64252c314b646/mujoco-3.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:2f71cb614559cd06a8ce57bd255146e15ccb49ee7dea316aeb895838ba82e1e0", size = 17719988, upload-time = "2026-06-22T17:39:59.614Z" }, + { url = "https://files.pythonhosted.org/packages/45/bd/c3a4ad6884e60bbbff3d77df75358570bcf9b97ff8d81a0ea3b311b0276e/mujoco-3.10.0-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:62b7e9faf714f1582e1dd923ba3ea769a939cc572f6d77909752cbb31db5409f", size = 7758349, upload-time = "2026-06-22T17:40:02.329Z" }, + { url = "https://files.pythonhosted.org/packages/41/69/4c55c05fe602d72be5526d911e6802de1e67ad70c9301f556eef1d78a4bb/mujoco-3.10.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0b812e0e36e8b2dde8ce4ad9b25e189b658b9c94f5e798aa64783261abb88321", size = 19348907, upload-time = "2026-06-22T17:40:04.634Z" }, + { url = "https://files.pythonhosted.org/packages/05/19/a8a560f29f7f0137da6d41d633bc892a9e8ebc36af64c31a3db5882d26d8/mujoco-3.10.0-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c0eff9fb64c39c21f5e29f39996c152ed9a2a5c783818b524c77abf41f6e5751", size = 19654107, upload-time = "2026-06-22T17:40:07.594Z" }, + { url = "https://files.pythonhosted.org/packages/b1/07/a37fc7fa55d38e9225884b80c3d241e669357e83f7e23f80b8860a7e14cd/mujoco-3.10.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5489e18a8dd09da2dd71d28563e17889a2e5a0ad07943ebcaa1446d6c4d4e1dd", size = 20789312, upload-time = "2026-06-22T17:40:10.656Z" }, + { url = "https://files.pythonhosted.org/packages/82/84/6548d32afc49fb79015a0b98d1119628ce94dd8befb4526c2cd10429e13f/mujoco-3.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:45a27a8982e6f89f09e87e4578a5d3e7c9b5667f3db07052e518993b78c9b3ea", size = 17757305, upload-time = "2026-06-22T17:40:13.448Z" }, + { url = "https://files.pythonhosted.org/packages/03/a2/4dd9f4cec6ce92f836a8b2de1cc799c4458af1467d7a044ef8014217bdb4/mujoco-3.10.0-cp312-cp312-macosx_10_16_x86_64.whl", hash = "sha256:47d4a22b7667c60e24e7ef6acb027c13abe9abba9acf17cc8db6fb250ba275ea", size = 7772567, upload-time = "2026-06-22T17:40:16.539Z" }, + { url = "https://files.pythonhosted.org/packages/5d/7d/ebe5342c136de27e0c430ba781f829df2cd66c00ed22627c1964fbd5d7fe/mujoco-3.10.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a4d35e9d0b13ff9ad3196294a7dac363f1d0cdaa988832d0b687d42d98f4ee29", size = 19380823, upload-time = "2026-06-22T17:40:19.211Z" }, + { url = "https://files.pythonhosted.org/packages/d8/5d/43d1b2b9fe97676e5af03020e132ac497b45a0333a4c61de657d0d52170a/mujoco-3.10.0-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cb7f0d7c148a588f3633020807fc0ec3f3a9aff1f647406e3e0ffe96b05dfd57", size = 19705628, upload-time = "2026-06-22T17:40:22.549Z" }, + { url = "https://files.pythonhosted.org/packages/c3/11/c69199e4123935f98068ab6ab6b35955b4de0f6a91d3f9883805a5789394/mujoco-3.10.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:966d12f88e77e2b188e7530667b519d6963b9b906cff83bab534e5e4279325a0", size = 20904309, upload-time = "2026-06-22T17:40:25.861Z" }, + { url = "https://files.pythonhosted.org/packages/47/13/07bf2550c7dcd69ee8c7fd1f5c400a4ba2e4ede0a29a463ad3ac4cc9da90/mujoco-3.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:708edb5aceee96f2767b1072641523060043b2c67000e39e6e9797addf073696", size = 17865123, upload-time = "2026-06-22T17:40:28.996Z" }, ] [[package]] @@ -5425,77 +5575,69 @@ wheels = [ [[package]] name = "nvidia-cublas-cu12" -version = "12.8.4.1" +version = "12.4.5.8" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/dc/61/e24b560ab2e2eaeb3c839129175fb330dfcfc29e5203196e5541a4c44682/nvidia_cublas_cu12-12.8.4.1-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:8ac4e771d5a348c551b2a426eda6193c19aa630236b418086020df5ba9667142", size = 594346921, upload-time = "2025-03-07T01:44:31.254Z" }, + { url = "https://files.pythonhosted.org/packages/ae/71/1c91302526c45ab494c23f61c7a84aa568b8c1f9d196efa5993957faf906/nvidia_cublas_cu12-12.4.5.8-py3-none-manylinux2014_x86_64.whl", hash = "sha256:2fc8da60df463fdefa81e323eef2e36489e1c94335b5358bcb38360adf75ac9b", size = 363438805, upload-time = "2024-04-03T20:57:06.025Z" }, ] [[package]] name = "nvidia-cuda-cupti-cu12" -version = "12.8.90" +version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/f8/02/2adcaa145158bf1a8295d83591d22e4103dbfd821bcaf6f3f53151ca4ffa/nvidia_cuda_cupti_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ea0cb07ebda26bb9b29ba82cda34849e73c166c18162d3913575b0c9db9a6182", size = 10248621, upload-time = "2025-03-07T01:40:21.213Z" }, + { url = "https://files.pythonhosted.org/packages/67/42/f4f60238e8194a3106d06a058d494b18e006c10bb2b915655bd9f6ea4cb1/nvidia_cuda_cupti_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:9dec60f5ac126f7bb551c055072b69d85392b13311fcc1bcda2202d172df30fb", size = 13813957, upload-time = "2024-04-03T20:55:01.564Z" }, ] [[package]] name = "nvidia-cuda-nvrtc-cu12" -version = "12.8.93" +version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/05/6b/32f747947df2da6994e999492ab306a903659555dddc0fbdeb9d71f75e52/nvidia_cuda_nvrtc_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:a7756528852ef889772a84c6cd89d41dfa74667e24cca16bb31f8f061e3e9994", size = 88040029, upload-time = "2025-03-07T01:42:13.562Z" }, + { url = "https://files.pythonhosted.org/packages/2c/14/91ae57cd4db3f9ef7aa99f4019cfa8d54cb4caa7e00975df6467e9725a9f/nvidia_cuda_nvrtc_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a178759ebb095827bd30ef56598ec182b85547f1508941a3d560eb7ea1fbf338", size = 24640306, upload-time = "2024-04-03T20:56:01.463Z" }, ] [[package]] name = "nvidia-cuda-runtime-cu12" -version = "12.8.90" +version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/0d/9b/a997b638fcd068ad6e4d53b8551a7d30fe8b404d6f1804abf1df69838932/nvidia_cuda_runtime_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:adade8dcbd0edf427b7204d480d6066d33902cab2a4707dcfc48a2d0fd44ab90", size = 954765, upload-time = "2025-03-07T01:40:01.615Z" }, + { url = "https://files.pythonhosted.org/packages/ea/27/1795d86fe88ef397885f2e580ac37628ed058a92ed2c39dc8eac3adf0619/nvidia_cuda_runtime_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:64403288fa2136ee8e467cdc9c9427e0434110899d07c779f25b5c068934faa5", size = 883737, upload-time = "2024-04-03T20:54:51.355Z" }, ] [[package]] name = "nvidia-cudnn-cu12" -version = "9.10.2.21" +version = "9.1.0.70" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/ba/51/e123d997aa098c61d029f76663dedbfb9bc8dcf8c60cbd6adbe42f76d049/nvidia_cudnn_cu12-9.10.2.21-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:949452be657fa16687d0930933f032835951ef0892b37d2d53824d1a84dc97a8", size = 706758467, upload-time = "2025-06-06T21:54:08.597Z" }, + { url = "https://files.pythonhosted.org/packages/9f/fd/713452cd72343f682b1c7b9321e23829f00b842ceaedcda96e742ea0b0b3/nvidia_cudnn_cu12-9.1.0.70-py3-none-manylinux2014_x86_64.whl", hash = "sha256:165764f44ef8c61fcdfdfdbe769d687e06374059fbb388b6c89ecb0e28793a6f", size = 664752741, upload-time = "2024-04-22T15:24:15.253Z" }, ] [[package]] name = "nvidia-cufft-cu12" -version = "11.3.3.83" +version = "11.2.1.3" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/1f/13/ee4e00f30e676b66ae65b4f08cb5bcbb8392c03f54f2d5413ea99a5d1c80/nvidia_cufft_cu12-11.3.3.83-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:4d2dd21ec0b88cf61b62e6b43564355e5222e4a3fb394cac0db101f2dd0d4f74", size = 193118695, upload-time = "2025-03-07T01:45:27.821Z" }, -] - -[[package]] -name = "nvidia-cufile-cu12" -version = "1.13.1.3" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/bb/fe/1bcba1dfbfb8d01be8d93f07bfc502c93fa23afa6fd5ab3fc7c1df71038a/nvidia_cufile_cu12-1.13.1.3-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1d069003be650e131b21c932ec3d8969c1715379251f8d23a1860554b1cb24fc", size = 1197834, upload-time = "2025-03-07T01:45:50.723Z" }, + { url = "https://files.pythonhosted.org/packages/27/94/3266821f65b92b3138631e9c8e7fe1fb513804ac934485a8d05776e1dd43/nvidia_cufft_cu12-11.2.1.3-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f083fc24912aa410be21fa16d157fed2055dab1cc4b6934a0e03cba69eb242b9", size = 211459117, upload-time = "2024-04-03T20:57:40.402Z" }, ] [[package]] name = "nvidia-curand-cu12" -version = "10.3.9.90" +version = "10.3.5.147" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/fb/aa/6584b56dc84ebe9cf93226a5cde4d99080c8e90ab40f0c27bda7a0f29aa1/nvidia_curand_cu12-10.3.9.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:b32331d4f4df5d6eefa0554c565b626c7216f87a06a4f56fab27c3b68a830ec9", size = 63619976, upload-time = "2025-03-07T01:46:23.323Z" }, + { url = "https://files.pythonhosted.org/packages/8a/6d/44ad094874c6f1b9c654f8ed939590bdc408349f137f9b98a3a23ccec411/nvidia_curand_cu12-10.3.5.147-py3-none-manylinux2014_x86_64.whl", hash = "sha256:a88f583d4e0bb643c49743469964103aa59f7f708d862c3ddb0fc07f851e3b8b", size = 56305206, upload-time = "2024-04-03T20:58:08.722Z" }, ] [[package]] name = "nvidia-cusolver-cu12" -version = "11.7.3.90" +version = "11.6.1.9" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nvidia-cublas-cu12", marker = "platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'" }, @@ -5503,58 +5645,50 @@ dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/85/48/9a13d2975803e8cf2777d5ed57b87a0b6ca2cc795f9a4f59796a910bfb80/nvidia_cusolver_cu12-11.7.3.90-py3-none-manylinux_2_27_x86_64.whl", hash = "sha256:4376c11ad263152bd50ea295c05370360776f8c3427b30991df774f9fb26c450", size = 267506905, upload-time = "2025-03-07T01:47:16.273Z" }, + { url = "https://files.pythonhosted.org/packages/3a/e1/5b9089a4b2a4790dfdea8b3a006052cfecff58139d5a4e34cb1a51df8d6f/nvidia_cusolver_cu12-11.6.1.9-py3-none-manylinux2014_x86_64.whl", hash = "sha256:19e33fa442bcfd085b3086c4ebf7e8debc07cfe01e11513cc6d332fd918ac260", size = 127936057, upload-time = "2024-04-03T20:58:28.735Z" }, ] [[package]] name = "nvidia-cusparse-cu12" -version = "12.5.8.93" +version = "12.3.1.170" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "nvidia-nvjitlink-cu12", marker = "platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/c2/f5/e1854cb2f2bcd4280c44736c93550cc300ff4b8c95ebe370d0aa7d2b473d/nvidia_cusparse_cu12-12.5.8.93-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:1ec05d76bbbd8b61b06a80e1eaf8cf4959c3d4ce8e711b65ebd0443bb0ebb13b", size = 288216466, upload-time = "2025-03-07T01:48:13.779Z" }, + { url = "https://files.pythonhosted.org/packages/db/f7/97a9ea26ed4bbbfc2d470994b8b4f338ef663be97b8f677519ac195e113d/nvidia_cusparse_cu12-12.3.1.170-py3-none-manylinux2014_x86_64.whl", hash = "sha256:ea4f11a2904e2a8dc4b1833cc1b5181cde564edd0d5cd33e3c168eff2d1863f1", size = 207454763, upload-time = "2024-04-03T20:58:59.995Z" }, ] [[package]] name = "nvidia-cusparselt-cu12" -version = "0.7.1" +version = "0.6.2" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/56/79/12978b96bd44274fe38b5dde5cfb660b1d114f70a65ef962bcbbed99b549/nvidia_cusparselt_cu12-0.7.1-py3-none-manylinux2014_x86_64.whl", hash = "sha256:f1bb701d6b930d5a7cea44c19ceb973311500847f81b634d802b7b539dc55623", size = 287193691, upload-time = "2025-02-26T00:15:44.104Z" }, + { url = "https://files.pythonhosted.org/packages/78/a8/bcbb63b53a4b1234feeafb65544ee55495e1bb37ec31b999b963cbccfd1d/nvidia_cusparselt_cu12-0.6.2-py3-none-manylinux2014_x86_64.whl", hash = "sha256:df2c24502fd76ebafe7457dbc4716b2fec071aabaed4fb7691a201cde03704d9", size = 150057751, upload-time = "2024-07-23T02:35:53.074Z" }, ] [[package]] name = "nvidia-nccl-cu12" -version = "2.27.5" +version = "2.21.5" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/6e/89/f7a07dc961b60645dbbf42e80f2bc85ade7feb9a491b11a1e973aa00071f/nvidia_nccl_cu12-2.27.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ad730cf15cb5d25fe849c6e6ca9eb5b76db16a80f13f425ac68d8e2e55624457", size = 322348229, upload-time = "2025-06-26T04:11:28.385Z" }, + { url = "https://files.pythonhosted.org/packages/df/99/12cd266d6233f47d00daf3a72739872bdc10267d0383508b0b9c84a18bb6/nvidia_nccl_cu12-2.21.5-py3-none-manylinux2014_x86_64.whl", hash = "sha256:8579076d30a8c24988834445f8d633c697d42397e92ffc3f63fa26766d25e0a0", size = 188654414, upload-time = "2024-04-03T15:32:57.427Z" }, ] [[package]] name = "nvidia-nvjitlink-cu12" -version = "12.8.93" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f6/74/86a07f1d0f42998ca31312f998bd3b9a7eff7f52378f4f270c8679c77fb9/nvidia_nvjitlink_cu12-12.8.93-py3-none-manylinux2010_x86_64.manylinux_2_12_x86_64.whl", hash = "sha256:81ff63371a7ebd6e6451970684f916be2eab07321b73c9d244dc2b4da7f73b88", size = 39254836, upload-time = "2025-03-07T01:49:55.661Z" }, -] - -[[package]] -name = "nvidia-nvshmem-cu12" -version = "3.4.5" +version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/b5/09/6ea3ea725f82e1e76684f0708bbedd871fc96da89945adeba65c3835a64c/nvidia_nvshmem_cu12-3.4.5-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:042f2500f24c021db8a06c5eec2539027d57460e1c1a762055a6554f72c369bd", size = 139103095, upload-time = "2025-09-06T00:32:31.266Z" }, + { url = "https://files.pythonhosted.org/packages/ff/ff/847841bacfbefc97a00036e0fce5a0f086b640756dc38caea5e1bb002655/nvidia_nvjitlink_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:06b3b9b25bf3f8af351d664978ca26a16d2c5127dbd53c0497e28d1fb9611d57", size = 21066810, upload-time = "2024-04-03T20:59:46.957Z" }, ] [[package]] name = "nvidia-nvtx-cu12" -version = "12.8.90" +version = "12.4.127" source = { registry = "https://pypi.org/simple" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/eb/86626c1bbc2edb86323022371c39aa48df6fd8b0a1647bc274577f72e90b/nvidia_nvtx_cu12-12.8.90-py3-none-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:5b17e2001cc0d751a5bc2c6ec6d26ad95913324a4adb86788c944f8ce9ba441f", size = 89954, upload-time = "2025-03-07T01:42:44.131Z" }, + { url = "https://files.pythonhosted.org/packages/87/20/199b8713428322a2f22b722c62b8cc278cc53dffa9705d744484b5035ee9/nvidia_nvtx_cu12-12.4.127-py3-none-manylinux2014_x86_64.whl", hash = "sha256:781e950d9b9f60d8241ccea575b32f5105a5baf4c2351cab5256a24869f12a1a", size = 99144, upload-time = "2024-04-03T20:56:12.406Z" }, ] [[package]] @@ -5741,7 +5875,8 @@ dependencies = [ { name = "tiktoken" }, { name = "torch" }, { name = "tqdm" }, - { name = "triton", marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or sys_platform == 'linux2'" }, + { name = "triton", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "(platform_machine == 'x86_64' and sys_platform == 'linux') or (platform_machine != 'aarch64' and sys_platform == 'linux2')" }, + { name = "triton", version = "3.6.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'aarch64' and sys_platform == 'linux2'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/35/8e/d36f8880bcf18ec026a55807d02fe4c7357da9f25aebd92f85178000c0dc/openai_whisper-20250625.tar.gz", hash = "sha256:37a91a3921809d9f44748ffc73c0a55c9f366c85a3ef5c2ae0cc09540432eb96", size = 803191, upload-time = "2025-06-26T01:06:13.34Z" } @@ -7010,6 +7145,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5f/90/7d766d54bb95939725e9a9361f9c06b0cfbe3fe100aa35400f0a461a278a/pygame-2.6.1-cp312-cp312-win_amd64.whl", hash = "sha256:3a9e7396be0d9633831c3f8d5d82dd63ba373ad65599628294b7a4f8a5a01a65", size = 10624591, upload-time = "2024-09-29T11:52:54.489Z" }, ] +[[package]] +name = "pyglet" +version = "2.1.15" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/26/ee/5caebffaf0345e5ccf342c30605898bd07654cee626154f7af2e465cc81c/pyglet-2.1.15.tar.gz", hash = "sha256:0ef34fe730808a97e48c24dd6ed4ff614024b980955ed8dbc2dd01ededaa08cb", size = 6597005, upload-time = "2026-06-28T11:39:09.98Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/1e/a8/9a203001fa496934708baf6cadc54b62e453f1256d5d88589c71019dd751/pyglet-2.1.15-py3-none-any.whl", hash = "sha256:8709131baf5e96c496d38197ff9f9207ad06f273c9694570008b145c58bf51ae", size = 1036800, upload-time = "2026-06-28T11:39:04.717Z" }, +] + [[package]] name = "pygments" version = "2.19.2" @@ -7264,6 +7408,30 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a6/9b/f81c8009a3bf8cd2b1d1ce74321c6f8bdb7d7075895fb04800f3795b431d/pyrealsense2_extended-2.58.1.10581.post1-cp312-cp312-win_amd64.whl", hash = "sha256:76ddf1dadd4dd8c542d4249d50dc4507962808f9ae3b6e807f317f319abeead3", size = 8754299, upload-time = "2026-05-31T20:50:09.02Z" }, ] +[[package]] +name = "pyrender" +version = "0.1.45" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "freetype-py" }, + { name = "imageio" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pillow" }, + { name = "pyglet" }, + { name = "pyopengl" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "six" }, + { name = "trimesh" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/4d/5a/2a3e5bfd83071a81e02291288391e0fa2c85d1c6765357f4de2dbc27bca6/pyrender-0.1.45.tar.gz", hash = "sha256:284b2432bf6832f05c5216c4b979ceb514ea78163bf53b8ce2bdf0069cb3b92e", size = 1202386, upload-time = "2021-02-18T18:56:28.82Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/28/88/174c28b9d3d03cf6d8edb6f637458f30f1cf1a2bd7a617cbd9dadb1740f6/pyrender-0.1.45-py3-none-any.whl", hash = "sha256:5cf751d1f21fba4640e830cef3a0b5a95ed0f05677bf92c6b8330056b4023aeb", size = 1214061, upload-time = "2021-02-18T18:56:27.275Z" }, +] + [[package]] name = "pysocks" version = "1.7.1" @@ -7438,6 +7606,33 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/aa/54/0cce26da03a981f949bb8449c9778537f75f5917c172e1d2992ff25cb57d/python_engineio-4.13.1-py3-none-any.whl", hash = "sha256:f32ad10589859c11053ad7d9bb3c9695cdf862113bfb0d20bc4d890198287399", size = 59847, upload-time = "2026-02-06T23:38:04.861Z" }, ] +[[package]] +name = "python-fcl" +version = "0.7.0.11" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cython" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/37/84/0416ef5aefa28b3bd289e492c0f03019da733605f31d936466ff1ef4a373/python_fcl-0.7.0.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e821a003f47a43e4282996f16361c42f6d73d7663f99b80228909556048a4647", size = 2002571, upload-time = "2026-04-08T05:10:06.615Z" }, + { url = "https://files.pythonhosted.org/packages/95/50/90cf468312733685e1e4e4c32b1cd9fd2fd4dd039260826655b4f8a463e2/python_fcl-0.7.0.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5fe1a0f453cbcb074896fe926101417b29a1e60e19844c0f2987ffe1c2315c99", size = 1567709, upload-time = "2026-04-08T05:10:08.246Z" }, + { url = "https://files.pythonhosted.org/packages/60/a2/62a6c10cc3af052a64e5c4631fec9fc3b01f9f06c59d5ee3715d44ba9c2d/python_fcl-0.7.0.11-cp310-cp310-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f17a99f5686952452e1ac56729a33d8a2659b16b29eaae2a70ebc6f2b74a6842", size = 4465498, upload-time = "2026-04-08T05:10:09.979Z" }, + { url = "https://files.pythonhosted.org/packages/bf/a4/d9cf98b28370dd98ac36468e20a516987d34267d4127b286665147bf44c9/python_fcl-0.7.0.11-cp310-cp310-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:f12e028d8de44f9aa6a3b34d44ca6757c9ef9f252aa477161559fe2a91d5a47c", size = 4605211, upload-time = "2026-04-08T05:10:11.942Z" }, + { url = "https://files.pythonhosted.org/packages/57/57/1f2835b965b1adb676bc64556dd7c6ec36b91d3b399e9ce523235f8d33b5/python_fcl-0.7.0.11-cp310-cp310-win_amd64.whl", hash = "sha256:5afac7af59480ba2ca748516e877a4f344e7d80bfa7304d9e99eed0d10ef2b7d", size = 1097582, upload-time = "2026-04-08T05:10:13.48Z" }, + { url = "https://files.pythonhosted.org/packages/99/fc/45e2986cfb39a000b153cdff5e9cb404e45a20949986610013b65ea1917f/python_fcl-0.7.0.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:1bd8aef44b2d1ba8a6beefcb41b9d6a797f72f8514acd0a9819fa2ed1b1ee277", size = 2002464, upload-time = "2026-04-08T05:10:14.967Z" }, + { url = "https://files.pythonhosted.org/packages/de/cf/612186d220d71c01cf07b17ecdd068d9ea0d4808f4cad994ef321fcaa4e7/python_fcl-0.7.0.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce515e599fe51e05e13df963339e75874529cb75c971b73319408acd5b2f7e5e", size = 1567431, upload-time = "2026-04-08T05:10:16.316Z" }, + { url = "https://files.pythonhosted.org/packages/72/ab/8be64abc477a3dd1da9341d610c391dd5266c9fce61871ac3901494cf059/python_fcl-0.7.0.11-cp311-cp311-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b1c7ec8b1c7a1e983a18bfb6d3a1c812c6de94f7b742f9c70b94f79397f57c14", size = 4527670, upload-time = "2026-04-08T05:10:17.697Z" }, + { url = "https://files.pythonhosted.org/packages/99/3a/38ece5c5e171e82601b98a2a728592fd906e007068070395d734d3d45744/python_fcl-0.7.0.11-cp311-cp311-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:08196821a2d903d389eb69ced3de4cc0352ed926074909bcff17d20256752d38", size = 4666250, upload-time = "2026-04-08T05:10:19.22Z" }, + { url = "https://files.pythonhosted.org/packages/d8/52/8a639d5b8650f0e88fdd296cd7e49064ea18800c3f6e99f4b02ed81591d2/python_fcl-0.7.0.11-cp311-cp311-win_amd64.whl", hash = "sha256:131f6b0621fe0a57735ec5318f7e4aa705299c568aed48ff3bb1030ffd824cd3", size = 1097590, upload-time = "2026-04-08T05:10:21.248Z" }, + { url = "https://files.pythonhosted.org/packages/1a/f1/320e1041a27460d390fe2a2cf1ce74f7fa381f1af36a6fc66aa6efe16c17/python_fcl-0.7.0.11-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:1e39419c089477a5be17606b9ffc90a390c823f2b81cf66fb6288cdbcce79c85", size = 2000323, upload-time = "2026-04-08T05:10:23.034Z" }, + { url = "https://files.pythonhosted.org/packages/2b/b9/b3da9b16d6213db4d932dddc82282cb539d0d85a41581f548efec7f7ae37/python_fcl-0.7.0.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:47238454103b8e5e66ca285aae84aa9c2386ae4fe3f2d392f1cbabe24bd69b1a", size = 1568204, upload-time = "2026-04-08T05:10:24.924Z" }, + { url = "https://files.pythonhosted.org/packages/17/77/704a3bb182b59dbe26b9836c00ce2e53a3877a21a6e7dfbfc404ab1fefe1/python_fcl-0.7.0.11-cp312-cp312-manylinux_2_24_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a69a007099d610d31290d7638750545c18c54b94c0cc1f0f420fc27740761f69", size = 4494148, upload-time = "2026-04-08T05:10:26.31Z" }, + { url = "https://files.pythonhosted.org/packages/88/5d/f13c1c8eed4ce3b9e1f59c6dd31850540e575390cd85917a74afc027a3c0/python_fcl-0.7.0.11-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:3ac7b02f47d7fb881e5786be8328da3c586af9b8c2f0eb07c4a3ed9342f810e2", size = 4659289, upload-time = "2026-04-08T05:10:27.815Z" }, + { url = "https://files.pythonhosted.org/packages/20/3d/27bf74bbb5318ecb224c06697bbbb8bee98b43ab9db9d375b305da11301a/python_fcl-0.7.0.11-cp312-cp312-win_amd64.whl", hash = "sha256:63c662c8ff30eeb78913624a4ac56209a6061248ed97066c3b744255d943299f", size = 1097547, upload-time = "2026-04-08T05:10:29.166Z" }, +] + [[package]] name = "python-lsp-jsonrpc" version = "1.1.2" @@ -7581,6 +7776,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/c4/34e93fe5f5429d7570ec1fa436f1986fb1f00c3e0f43a589fe2bbcd22c3f/pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00", size = 509225, upload-time = "2025-03-25T02:24:58.468Z" }, ] +[[package]] +name = "pyvers" +version = "0.2.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/62/21/9daf8da2793d112a04b46ac33ab9046c91b0af8292cd93b3cfdd07ff7169/pyvers-0.2.3.tar.gz", hash = "sha256:c4b81c3a033963245e124cdecb052783c9c4cea3bb08c051833af1c44faa6283", size = 12347, upload-time = "2026-07-09T13:58:07.046Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/2d/8d/63787e8feda59d981ebc953b6581ccab3c97cf7bfe0a6a407c07b50fc86c/pyvers-0.2.3-py3-none-any.whl", hash = "sha256:6f5b5612f2f4bd08caa49baa70fc5f875fc7da701a5385c13e244eea6b8114dd", size = 11757, upload-time = "2026-07-09T13:58:06.078Z" }, +] + [[package]] name = "pywin32" version = "311" @@ -8057,28 +8261,56 @@ wheels = [ [[package]] name = "safetensors" -version = "0.7.0" +version = "0.8.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/45/06/f955dbbb1859e3bd23c8ac6141af5106e7ad5fedec4a3a6e3d60f94b7001/safetensors-0.8.0.tar.gz", hash = "sha256:fabaf3e0f18a6618d9b36560682562157f77c2b71fcffc7b432be2baed9d753d", size = 325846, upload-time = "2026-06-09T07:52:25.563Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/39/a0/f718cda65b05407d228f97602cf60dca269c979867aa5beb25410de26cd3/safetensors-0.8.0-cp310-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c554f85858e05226d3c2828e32395e677434685d6d94594a41643361c5e837f0", size = 473568, upload-time = "2026-06-09T07:52:18.829Z" }, + { url = "https://files.pythonhosted.org/packages/f5/b1/fa7c600e7dceae12e9606c7578cbc9ff1e1ed55844883ee5c92205e86226/safetensors-0.8.0-cp310-abi3-macosx_11_0_arm64.whl", hash = "sha256:c80201d22cbf405b80647a60ada77bba06c8fba2da2743ba1e89cdcc39a81f25", size = 484562, upload-time = "2026-06-09T07:52:17.518Z" }, + { url = "https://files.pythonhosted.org/packages/09/7d/65a7de0af421317bb36a067241e4235fff194eed60b961ed6d3f59a3fc60/safetensors-0.8.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7a46e5ff292c356d6991e60942ba7f79817682d3a2cef0702136448cb9c4d235", size = 502844, upload-time = "2026-06-09T07:52:07.624Z" }, + { url = "https://files.pythonhosted.org/packages/91/4f/3175c9d75634e0e0dda0082794193521035edd7c70a6f212bf33ca06ddf4/safetensors-0.8.0-cp310-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:4124502b78f03534117c848f87a39b8f31e577b15eff423bf8bfb95f2a8c30d0", size = 511823, upload-time = "2026-06-09T07:52:09.565Z" }, + { url = "https://files.pythonhosted.org/packages/20/87/846c289e7aa2299eff406335717cf43ce8777194ece8aad75772e0411615/safetensors-0.8.0-cp310-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7bc0a787ba8a35be368ee3574edfa2b1ad389eebd0a72e482ae275490e3f6c98", size = 633461, upload-time = "2026-06-09T07:52:11.128Z" }, + { url = "https://files.pythonhosted.org/packages/76/22/8d64d9df2c45d5ded401df889d0ad90882804ca172d79ec4f0df8f727fe0/safetensors-0.8.0-cp310-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:040070828e36dc8e122178bbbd5830ff9e97920affb84cbe0f46442497bed358", size = 545148, upload-time = "2026-06-09T07:52:13.603Z" }, + { url = "https://files.pythonhosted.org/packages/28/50/f203ff3a3ddfe19308efc83c5a3a29ed02bf786732ec35e68bf9162f3365/safetensors-0.8.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd6f3f93c9a0a7cc2788ee63fb763353d4bd2e89b0751bc78fcf7dda00bea774", size = 516040, upload-time = "2026-06-09T07:52:16.29Z" }, + { url = "https://files.pythonhosted.org/packages/46/fb/cdaed17ceb2948784fd9c36b6fd3e951b608547cea81a48e8ee6f8cfdfcb/safetensors-0.8.0-cp310-abi3-manylinux_2_31_riscv64.whl", hash = "sha256:fcdd41ec4628fee5799f807c73c353629130fbd942aa23d83c623dd6c9d52d78", size = 513832, upload-time = "2026-06-09T07:52:12.37Z" }, + { url = "https://files.pythonhosted.org/packages/0d/49/1e15de264dcc3b77943d2d0c56a95809956883b1c2d6d585c792523f180b/safetensors-0.8.0-cp310-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8e9f537aa183a38ace122d27303dcd986b26bd2a7591f9181d7f0c396f4677ca", size = 559930, upload-time = "2026-06-09T07:52:14.743Z" }, + { url = "https://files.pythonhosted.org/packages/2a/43/bf38443278eab4b1be1fce2931e2b012ad9cb7df52ada751d0aab8f7659a/safetensors-0.8.0-cp310-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:87eec7ffed2b809f05a398a8becb7d013f19f7837cd15d9748580d6cf30dbaf4", size = 678670, upload-time = "2026-06-09T07:52:20.032Z" }, + { url = "https://files.pythonhosted.org/packages/72/e3/68cd3fa5b48488e84add63e04cb12f3bc28ae4638c06d4508c6e88823d0e/safetensors-0.8.0-cp310-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:4a95ae2b05d7726d751da4ebf626a2ca782b706e101bd894c95bc2450b1cffcc", size = 786679, upload-time = "2026-06-09T07:52:21.322Z" }, + { url = "https://files.pythonhosted.org/packages/29/4b/1c19c509d56e01f4fbb3d0a2e597450f6cc04d1d56cf52defb0a62dfd715/safetensors-0.8.0-cp310-abi3-musllinux_1_2_i686.whl", hash = "sha256:3ae091f16662658bdc019a4ff6cb4c085bb7d725eb5978b183ffd265863b6d2d", size = 765683, upload-time = "2026-06-09T07:52:22.594Z" }, + { url = "https://files.pythonhosted.org/packages/27/43/41c1621732edd934d868a00d1b891584c892a7b62a9aab82ea5a0a5623ee/safetensors-0.8.0-cp310-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:8e080062fcde23be189565e1c3305d16751a218ecf9412c8601e64204eb6f846", size = 722361, upload-time = "2026-06-09T07:52:23.924Z" }, + { url = "https://files.pythonhosted.org/packages/8e/3f/73ccf82579412b4a71c4ca673f10b5f1f888d7cf5af7fe24f27d30307be4/safetensors-0.8.0-cp310-abi3-win32.whl", hash = "sha256:2ddf52eac562eda224f99acfa7889d02968c1fd59a5b011ae7d8137c37e9c02d", size = 342401, upload-time = "2026-06-09T07:52:28.895Z" }, + { url = "https://files.pythonhosted.org/packages/1b/6d/3fba214c1e5e0f69991677ec3bc17023f0421776975e1de0c682dca475e2/safetensors-0.8.0-cp310-abi3-win_amd64.whl", hash = "sha256:096ec1a98435df7beb08853bb5aa9081a84f23d0adc67ed1a0a10550f608373f", size = 355540, upload-time = "2026-06-09T07:52:27.832Z" }, + { url = "https://files.pythonhosted.org/packages/8d/fc/7eedc3510d97878876e32774eebbeb61c43f148a96e915c84229a3e967aa/safetensors-0.8.0-cp310-abi3-win_arm64.whl", hash = "sha256:f7838e5135a406ad3e02efdcb8cf2e5397d368b0154537c4fec682dbc544d452", size = 340500, upload-time = "2026-06-09T07:52:26.745Z" }, +] + +[[package]] +name = "scene-synthesizer" +version = "1.15.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/29/9c/6e74567782559a63bd040a236edca26fd71bc7ba88de2ef35d75df3bca5e/safetensors-0.7.0.tar.gz", hash = "sha256:07663963b67e8bd9f0b8ad15bb9163606cd27cc5a1b96235a50d8369803b96b0", size = 200878, upload-time = "2025-11-19T15:18:43.199Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/fa/47/aef6c06649039accf914afef490268e1067ed82be62bcfa5b7e886ad15e8/safetensors-0.7.0-cp38-abi3-macosx_10_12_x86_64.whl", hash = "sha256:c82f4d474cf725255d9e6acf17252991c3c8aac038d6ef363a4bf8be2f6db517", size = 467781, upload-time = "2025-11-19T15:18:35.84Z" }, - { url = "https://files.pythonhosted.org/packages/e8/00/374c0c068e30cd31f1e1b46b4b5738168ec79e7689ca82ee93ddfea05109/safetensors-0.7.0-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:94fd4858284736bb67a897a41608b5b0c2496c9bdb3bf2af1fa3409127f20d57", size = 447058, upload-time = "2025-11-19T15:18:34.416Z" }, - { url = "https://files.pythonhosted.org/packages/f1/06/578ffed52c2296f93d7fd2d844cabfa92be51a587c38c8afbb8ae449ca89/safetensors-0.7.0-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e07d91d0c92a31200f25351f4acb2bc6aff7f48094e13ebb1d0fb995b54b6542", size = 491748, upload-time = "2025-11-19T15:18:09.79Z" }, - { url = "https://files.pythonhosted.org/packages/ae/33/1debbbb70e4791dde185edb9413d1fe01619255abb64b300157d7f15dddd/safetensors-0.7.0-cp38-abi3-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8469155f4cb518bafb4acf4865e8bb9d6804110d2d9bdcaa78564b9fd841e104", size = 503881, upload-time = "2025-11-19T15:18:16.145Z" }, - { url = "https://files.pythonhosted.org/packages/8e/1c/40c2ca924d60792c3be509833df711b553c60effbd91da6f5284a83f7122/safetensors-0.7.0-cp38-abi3-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:54bef08bf00a2bff599982f6b08e8770e09cc012d7bba00783fc7ea38f1fb37d", size = 623463, upload-time = "2025-11-19T15:18:21.11Z" }, - { url = "https://files.pythonhosted.org/packages/9b/3a/13784a9364bd43b0d61eef4bea2845039bc2030458b16594a1bd787ae26e/safetensors-0.7.0-cp38-abi3-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:42cb091236206bb2016d245c377ed383aa7f78691748f3bb6ee1bfa51ae2ce6a", size = 532855, upload-time = "2025-11-19T15:18:25.719Z" }, - { url = "https://files.pythonhosted.org/packages/a0/60/429e9b1cb3fc651937727befe258ea24122d9663e4d5709a48c9cbfceecb/safetensors-0.7.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dac7252938f0696ddea46f5e855dd3138444e82236e3be475f54929f0c510d48", size = 507152, upload-time = "2025-11-19T15:18:33.023Z" }, - { url = "https://files.pythonhosted.org/packages/3c/a8/4b45e4e059270d17af60359713ffd83f97900d45a6afa73aaa0d737d48b6/safetensors-0.7.0-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:1d060c70284127fa805085d8f10fbd0962792aed71879d00864acda69dbab981", size = 541856, upload-time = "2025-11-19T15:18:31.075Z" }, - { url = "https://files.pythonhosted.org/packages/06/87/d26d8407c44175d8ae164a95b5a62707fcc445f3c0c56108e37d98070a3d/safetensors-0.7.0-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:cdab83a366799fa730f90a4ebb563e494f28e9e92c4819e556152ad55e43591b", size = 674060, upload-time = "2025-11-19T15:18:37.211Z" }, - { url = "https://files.pythonhosted.org/packages/11/f5/57644a2ff08dc6325816ba7217e5095f17269dada2554b658442c66aed51/safetensors-0.7.0-cp38-abi3-musllinux_1_2_armv7l.whl", hash = "sha256:672132907fcad9f2aedcb705b2d7b3b93354a2aec1b2f706c4db852abe338f85", size = 771715, upload-time = "2025-11-19T15:18:38.689Z" }, - { url = "https://files.pythonhosted.org/packages/86/31/17883e13a814bd278ae6e266b13282a01049b0c81341da7fd0e3e71a80a3/safetensors-0.7.0-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:5d72abdb8a4d56d4020713724ba81dac065fedb7f3667151c4a637f1d3fb26c0", size = 714377, upload-time = "2025-11-19T15:18:40.162Z" }, - { url = "https://files.pythonhosted.org/packages/4a/d8/0c8a7dc9b41dcac53c4cbf9df2b9c83e0e0097203de8b37a712b345c0be5/safetensors-0.7.0-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:b0f6d66c1c538d5a94a73aa9ddca8ccc4227e6c9ff555322ea40bdd142391dd4", size = 677368, upload-time = "2025-11-19T15:18:41.627Z" }, - { url = "https://files.pythonhosted.org/packages/05/e5/cb4b713c8a93469e3c5be7c3f8d77d307e65fe89673e731f5c2bfd0a9237/safetensors-0.7.0-cp38-abi3-win32.whl", hash = "sha256:c74af94bf3ac15ac4d0f2a7c7b4663a15f8c2ab15ed0fc7531ca61d0835eccba", size = 326423, upload-time = "2025-11-19T15:18:45.74Z" }, - { url = "https://files.pythonhosted.org/packages/5d/e6/ec8471c8072382cb91233ba7267fd931219753bb43814cbc71757bfd4dab/safetensors-0.7.0-cp38-abi3-win_amd64.whl", hash = "sha256:d1239932053f56f3456f32eb9625590cc7582e905021f94636202a864d470755", size = 341380, upload-time = "2025-11-19T15:18:44.427Z" }, - { url = "https://files.pythonhosted.org/packages/a7/6a/4d08d89a6fcbe905c5ae68b8b34f0791850882fc19782d0d02c65abbdf3b/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f4729811a6640d019a4b7ba8638ee2fd21fa5ca8c7e7bdf0fed62068fcaac737", size = 492430, upload-time = "2025-11-19T15:18:11.884Z" }, - { url = "https://files.pythonhosted.org/packages/dd/29/59ed8152b30f72c42d00d241e58eaca558ae9dbfa5695206e2e0f54c7063/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:12f49080303fa6bb424b362149a12949dfbbf1e06811a88f2307276b0c131afd", size = 503977, upload-time = "2025-11-19T15:18:17.523Z" }, - { url = "https://files.pythonhosted.org/packages/d3/0b/4811bfec67fa260e791369b16dab105e4bae82686120554cc484064e22b4/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:0071bffba4150c2f46cae1432d31995d77acfd9f8db598b5d1a2ce67e8440ad2", size = 623890, upload-time = "2025-11-19T15:18:22.666Z" }, - { url = "https://files.pythonhosted.org/packages/58/5b/632a58724221ef03d78ab65062e82a1010e1bef8e8e0b9d7c6d7b8044841/safetensors-0.7.0-pp310-pypy310_pp73-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:473b32699f4200e69801bf5abf93f1a4ecd432a70984df164fc22ccf39c4a6f3", size = 531885, upload-time = "2025-11-19T15:18:27.146Z" }, +dependencies = [ + { name = "config-path" }, + { name = "matplotlib" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "python-fcl" }, + { name = "pyyaml" }, + { name = "rtree" }, + { name = "setuptools-scm" }, + { name = "shapely" }, + { name = "triangle" }, + { name = "trimesh" }, + { name = "yourdfpy" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/aa/cb/ccbf1bd9c303cc10153c06fed0dd7455052b146c507018178004c34de747/scene_synthesizer-1.15.0-py3-none-any.whl", hash = "sha256:1323006ff273c4a6d9bd119b7a2cb6ecfe6535c8d576c253d867a9c55cafa247", size = 183496, upload-time = "2025-06-28T01:19:22.322Z" }, +] + +[package.optional-dependencies] +recommend = [ + { name = "dm-control" }, + { name = "usd-core" }, ] [[package]] @@ -8090,14 +8322,15 @@ resolution-markers = [ "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform == 'win32'", "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'", "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'darwin'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform == 'win32'", "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", ] dependencies = [ - { name = "joblib", marker = "(python_full_version < '3.11' and platform_machine != 'aarch64') or (python_full_version < '3.11' and sys_platform != 'linux')" }, - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and platform_machine != 'aarch64') or (python_full_version < '3.11' and sys_platform != 'linux')" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version < '3.11' and platform_machine != 'aarch64') or (python_full_version < '3.11' and sys_platform != 'linux')" }, - { name = "threadpoolctl", marker = "(python_full_version < '3.11' and platform_machine != 'aarch64') or (python_full_version < '3.11' and sys_platform != 'linux')" }, + { name = "joblib", marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/98/c2/a7855e41c9d285dfe86dc50b250978105dce513d6e459ea66a6aeb0e1e0c/scikit_learn-1.7.2.tar.gz", hash = "sha256:20e9e49ecd130598f1ca38a1d85090e1a600147b9c02fa6f15d69cb53d968fda", size = 7193136, upload-time = "2025-09-09T08:21:29.075Z" } wheels = [ @@ -8130,17 +8363,19 @@ resolution-markers = [ "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform == 'win32'", "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'", "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'darwin'", + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform == 'win32'", "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'darwin'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'linux'", "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform == 'win32'", "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", ] dependencies = [ - { name = "joblib", marker = "(python_full_version >= '3.11' and platform_machine != 'aarch64') or (python_full_version >= '3.11' and sys_platform != 'linux')" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'aarch64') or (python_full_version >= '3.11' and sys_platform != 'linux')" }, - { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "(python_full_version >= '3.11' and platform_machine != 'aarch64') or (python_full_version >= '3.11' and sys_platform != 'linux')" }, - { name = "threadpoolctl", marker = "(python_full_version >= '3.11' and platform_machine != 'aarch64') or (python_full_version >= '3.11' and sys_platform != 'linux')" }, + { name = "joblib", marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "threadpoolctl", marker = "python_full_version >= '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/0e/d4/40988bf3b8e34feec1d0e6a051446b1f66225f8529b9309becaeef62b6c4/scikit_learn-1.8.0.tar.gz", hash = "sha256:9bccbb3b40e3de10351f8f5068e105d0f4083b1a65fa07b6634fbc401a6287fd", size = 7335585, upload-time = "2025-12-10T07:08:53.618Z" } wheels = [ @@ -8269,11 +8504,27 @@ wheels = [ [[package]] name = "setuptools" -version = "81.0.0" +version = "77.0.3" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/0d/1c/73e719955c59b8e424d015ab450f51c0af856ae46ea2da83eba51cc88de1/setuptools-81.0.0.tar.gz", hash = "sha256:487b53915f52501f0a79ccfd0c02c165ffe06631443a886740b91af4b7a5845a", size = 1198299, upload-time = "2026-02-06T21:10:39.601Z" } +sdist = { url = "https://files.pythonhosted.org/packages/81/ed/7101d53811fd359333583330ff976e5177c5e871ca8b909d1d6c30553aa3/setuptools-77.0.3.tar.gz", hash = "sha256:583b361c8da8de57403743e756609670de6fb2345920e36dc5c2d914c319c945", size = 1367236, upload-time = "2025-03-20T14:38:08.777Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/e1/e3/c164c88b2e5ce7b24d667b9bd83589cf4f3520d97cad01534cd3c4f55fdb/setuptools-81.0.0-py3-none-any.whl", hash = "sha256:fdd925d5c5d9f62e4b74b30d6dd7828ce236fd6ed998a08d81de62ce5a6310d6", size = 1062021, upload-time = "2026-02-06T21:10:37.175Z" }, + { url = "https://files.pythonhosted.org/packages/a9/07/99f2cefae815c66eb23148f15d79ec055429c38fa8986edcc712ab5f3223/setuptools-77.0.3-py3-none-any.whl", hash = "sha256:67122e78221da5cf550ddd04cf8742c8fe12094483749a792d56cd669d6cf58c", size = 1255678, upload-time = "2025-03-20T14:38:06.621Z" }, +] + +[[package]] +name = "setuptools-scm" +version = "10.2.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "packaging" }, + { name = "setuptools" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, + { name = "vcs-versioning" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/b1/d0b97ffd2856a7d19c63024a89fb84813cb9d2ed7fa8fdbedf9e2f13a9ab/setuptools_scm-10.2.1.tar.gz", hash = "sha256:4fa7dd82cf8c800df59c9a288c90299b1657ff1ecfc3f5cc00287c5dbf5e27a9", size = 154237, upload-time = "2026-07-21T08:08:04.553Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/b4/02ac1d9833b882c87b3a4e82703e70eac4ab33d0d15fb123e60bb01f3bc1/setuptools_scm-10.2.1-py3-none-any.whl", hash = "sha256:b7c82f4102d389ee57dc66ccdb4f9b4bca3c40ba83b43f1f63d68ccd72db2580", size = 29078, upload-time = "2026-07-21T08:08:03.293Z" }, ] [[package]] @@ -8312,6 +8563,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/bf/cb6c1c505cb31e818e900b9312d514f381fbfa5c4363edfce0fcc4f8c1a4/shapely-2.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:743044b4cfb34f9a67205cee9279feaf60ba7d02e69febc2afc609047cb49179", size = 1722861, upload-time = "2025-09-24T13:50:43.35Z" }, ] +[[package]] +name = "sharedarray" +version = "3.2.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/a6/d2/6818d35a7abba9b2410813f2160630e125b12a52ca11acfd1fb0959433ad/SharedArray-3.2.4.tar.gz", hash = "sha256:b8b8d189110c023b9de502f9396ff2591f660fb2c9637eb13fcaf233127e50be", size = 19584, upload-time = "2024-07-18T10:10:53.084Z" } + [[package]] name = "shellingham" version = "1.5.4" @@ -8526,25 +8787,16 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/45/a132b9074aa18e799b891b91ad72133c98d8042c70f6240e4c5f9dabee2f/structlog-25.5.0-py3-none-any.whl", hash = "sha256:a8453e9b9e636ec59bd9e79bbd4a72f025981b3ba0f5837aebf48f02f37a7f9f", size = 72510, upload-time = "2025-10-27T08:28:21.535Z" }, ] -[[package]] -name = "svg-path" -version = "7.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/66/b9/649abbe870842c185b12920e937e9b95d4c2b18de50af98d2c140df3e179/svg_path-7.0.tar.gz", hash = "sha256:9037486957cb1dcf4375ef42206499a47c111b8ffcbac6e3e55f9d079d875bb0", size = 23552, upload-time = "2025-07-06T15:20:40.823Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/3a/83/4f5b250220e1a5acd31345a5ec1c95a7769725d0d8135276f399f44062f8/svg_path-7.0-py2.py3-none-any.whl", hash = "sha256:447cb1e16a95acea2dd867fe737fa99cb75d587b4fc64dbee709a8dd6891ad9c", size = 18208, upload-time = "2025-07-06T15:20:39.59Z" }, -] - [[package]] name = "sympy" -version = "1.14.0" +version = "1.13.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mpmath" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/83/d3/803453b36afefb7c2bb238361cd4ae6125a569b4db67cd9e79846ba2d68c/sympy-1.14.0.tar.gz", hash = "sha256:d3d3fe8df1e5a0b42f0e7bdf50541697dbe7d23746e894990c030e2b05e72517", size = 7793921, upload-time = "2025-04-27T18:05:01.611Z" } +sdist = { url = "https://files.pythonhosted.org/packages/ca/99/5a5b6f19ff9f083671ddf7b9632028436167cd3d33e11015754e41b249a4/sympy-1.13.1.tar.gz", hash = "sha256:9cebf7e04ff162015ce31c9c6c9144daa34a93bd082f54fd8f12deca4f47515f", size = 7533040, upload-time = "2024-07-19T09:26:51.238Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a2/09/77d55d46fd61b4a135c444fc97158ef34a095e5681d0a6c10b75bf356191/sympy-1.14.0-py3-none-any.whl", hash = "sha256:e091cc3e99d2141a0ba2847328f5479b05d94a6635cb96148ccb3f34671bd8f5", size = 6299353, upload-time = "2025-04-27T18:04:59.103Z" }, + { url = "https://files.pythonhosted.org/packages/b2/fe/81695a1aa331a842b582453b605175f419fe8540355886031328089d840a/sympy-1.13.1-py3-none-any.whl", hash = "sha256:db36cdc64bf61b9b24578b6f7bab1ecdd2452cf008f34faa33776680c26d66f8", size = 6189177, upload-time = "2024-07-19T09:26:48.863Z" }, ] [[package]] @@ -8602,6 +8854,35 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e0/1d/b5d63f1a6b824282b57f7b581810d20b7a28ca951f2d5b59f1eb0782c12b/tensorboardx-2.6.4-py3-none-any.whl", hash = "sha256:5970cf3a1f0a6a6e8b180ccf46f3fe832b8a25a70b86e5a237048a7c0beb18e2", size = 87201, upload-time = "2025-06-10T22:37:05.44Z" }, ] +[[package]] +name = "tensordict" +version = "0.13.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cloudpickle" }, + { name = "importlib-metadata" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "orjson" }, + { name = "packaging" }, + { name = "pyvers" }, + { name = "torch" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/fb/65/81c5bfd5e410e908f183eb683c5d6fe284b98d3f6fd961f77065adb0f632/tensordict-0.13.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:5e76410e72525c47f668324d377c612ae75dcfb089555d529b88a92ece1bbd76", size = 908794, upload-time = "2026-06-04T14:46:45.983Z" }, + { url = "https://files.pythonhosted.org/packages/2f/52/b662ee78687cd127cb7cbab609227266584d30305653647098782f26653e/tensordict-0.13.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6538f8c2760d7f02223090f6e549d61ac7a3de39f413020aed9867e8529195d2", size = 553424, upload-time = "2026-06-04T14:46:47.762Z" }, + { url = "https://files.pythonhosted.org/packages/2d/7b/70fde071680a281a9de3b48c9fe879a2c4c1feabecf2dfe982ecbc9048a1/tensordict-0.13.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:405a2d65e1b8e070c6373a3ce1186852b7ec4c4fa798503d79fb603d143483ec", size = 557986, upload-time = "2026-06-04T14:46:49.307Z" }, + { url = "https://files.pythonhosted.org/packages/dc/95/49c903670d11a1cfaef8e8f12a9c59e692cfad187f4ebe9d0dff92b06dd1/tensordict-0.13.0-cp310-cp310-win_amd64.whl", hash = "sha256:e0983899f34096627bd5e2d6534bd4fd3ce1028dd8debe789a89f4e5b054e51e", size = 604764, upload-time = "2026-06-04T14:46:50.595Z" }, + { url = "https://files.pythonhosted.org/packages/21/7e/dbe7b63268e2dc1116f44f4bfa083eb28e7f1e264977c3503dd86a6802a1/tensordict-0.13.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:716a4b1b18e481566c0d0994a5a0afd02093600d41cefe7a221c9d0b92518741", size = 910806, upload-time = "2026-06-04T14:46:52.088Z" }, + { url = "https://files.pythonhosted.org/packages/30/de/3b12d62f7b4e654d5a0cc7a1f5f0a1884dc6adb2e6151d5f0bbf9222c11c/tensordict-0.13.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3181e0faf78f192e71aa35f1ab7b88f48714c3aba60198c7a7b8cefb18d7096e", size = 554682, upload-time = "2026-06-04T14:46:53.558Z" }, + { url = "https://files.pythonhosted.org/packages/7a/5a/953199b7dcae3409cef269abadfd90ff26798c3b03d7e291d2c0b505f76c/tensordict-0.13.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:be6b34404788ac6239ad347fb3e16222db79f716a4c3e43e5df378cdaeab4785", size = 559165, upload-time = "2026-06-04T14:46:54.954Z" }, + { url = "https://files.pythonhosted.org/packages/97/5f/dbd18a144034d2e9d8c2b5dbc1d4d2b463fb6b02d5e3adb84333f1fc3117/tensordict-0.13.0-cp311-cp311-win_amd64.whl", hash = "sha256:36a086188dbb11bcd80cf38e08101e788a59105eb10ae6492109d0c1797b8ee0", size = 607345, upload-time = "2026-06-04T14:46:56.822Z" }, + { url = "https://files.pythonhosted.org/packages/b8/77/6edc42425c4cc3755e52df25478f9b2ab7f67063e0bdc6c472a3db783f0a/tensordict-0.13.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:023d19f36b0aef990668033ca9dd721bace95266d0b9650626bdb4e3f2192f13", size = 911682, upload-time = "2026-06-04T14:46:58.304Z" }, + { url = "https://files.pythonhosted.org/packages/a8/b5/0c3889dabb6d92373036b9c2cc8269aa2fa5c3089f4ef1c04ae34d82b494/tensordict-0.13.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:25dbc418dbfe2733b34467c478f70c1d83870c76029397f00b5c45832d47929a", size = 555486, upload-time = "2026-06-04T14:46:59.802Z" }, + { url = "https://files.pythonhosted.org/packages/89/6c/92da62aefa186e2ab0e307dceccfbcdcb8769ec812a00676cb164ef6f47e/tensordict-0.13.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:343cf19838f6cb6be9db28d164338a399c269171854a6d688cb42f82be796e1c", size = 559537, upload-time = "2026-06-04T14:47:01.096Z" }, + { url = "https://files.pythonhosted.org/packages/48/33/fdbc52ec5069d64910c8efbf64a67fe399134ec9f602d4a3cfa5dd8e5362/tensordict-0.13.0-cp312-cp312-win_amd64.whl", hash = "sha256:53f60f72554b49d2143768c92396b8af020ab920d6068a46c115b11669444e9b", size = 608643, upload-time = "2026-06-04T14:47:02.466Z" }, +] + [[package]] name = "tensorstore" version = "0.1.78" @@ -8846,10 +9127,9 @@ wheels = [ [[package]] name = "torch" -version = "2.10.0" +version = "2.6.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "cuda-bindings", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "filelock" }, { name = "fsspec" }, { name = "jinja2" }, @@ -8861,39 +9141,52 @@ dependencies = [ { name = "nvidia-cuda-runtime-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cudnn-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cufft-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-cufile-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-curand-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cusolver-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cusparse-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-cusparselt-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nccl-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvjitlink-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, - { name = "nvidia-nvshmem-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "nvidia-nvtx-cu12", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "setuptools", marker = "python_full_version >= '3.12'" }, { name = "sympy" }, - { name = "triton", marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, + { name = "triton", version = "3.2.0", source = { registry = "https://pypi.org/simple" }, marker = "platform_machine == 'x86_64' and sys_platform == 'linux'" }, { name = "typing-extensions" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/5b/30/bfebdd8ec77db9a79775121789992d6b3b75ee5494971294d7b4b7c999bc/torch-2.10.0-2-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:2b980edd8d7c0a68c4e951ee1856334a43193f98730d97408fbd148c1a933313", size = 79411457, upload-time = "2026-02-10T21:44:59.189Z" }, - { url = "https://files.pythonhosted.org/packages/0f/8b/4b61d6e13f7108f36910df9ab4b58fd389cc2520d54d81b88660804aad99/torch-2.10.0-2-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:418997cb02d0a0f1497cf6a09f63166f9f5df9f3e16c8a716ab76a72127c714f", size = 79423467, upload-time = "2026-02-10T21:44:48.711Z" }, - { url = "https://files.pythonhosted.org/packages/d3/54/a2ba279afcca44bbd320d4e73675b282fcee3d81400ea1b53934efca6462/torch-2.10.0-2-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:13ec4add8c3faaed8d13e0574f5cd4a323c11655546f91fbe6afa77b57423574", size = 79498202, upload-time = "2026-02-10T21:44:52.603Z" }, - { url = "https://files.pythonhosted.org/packages/16/ee/efbd56687be60ef9af0c9c0ebe106964c07400eade5b0af8902a1d8cd58c/torch-2.10.0-3-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a1ff626b884f8c4e897c4c33782bdacdff842a165fee79817b1dd549fdda1321", size = 915510070, upload-time = "2026-03-11T14:16:39.386Z" }, - { url = "https://files.pythonhosted.org/packages/36/ab/7b562f1808d3f65414cd80a4f7d4bb00979d9355616c034c171249e1a303/torch-2.10.0-3-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:ac5bdcbb074384c66fa160c15b1ead77839e3fe7ed117d667249afce0acabfac", size = 915518691, upload-time = "2026-03-11T14:15:43.147Z" }, - { url = "https://files.pythonhosted.org/packages/b3/7a/abada41517ce0011775f0f4eacc79659bc9bc6c361e6bfe6f7052a6b9363/torch-2.10.0-3-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:98c01b8bb5e3240426dcde1446eed6f40c778091c8544767ef1168fc663a05a6", size = 915622781, upload-time = "2026-03-11T14:17:11.354Z" }, - { url = "https://files.pythonhosted.org/packages/0c/1a/c61f36cfd446170ec27b3a4984f072fd06dab6b5d7ce27e11adb35d6c838/torch-2.10.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5276fa790a666ee8becaffff8acb711922252521b28fbce5db7db5cf9cb2026d", size = 145992962, upload-time = "2026-01-21T16:24:14.04Z" }, - { url = "https://files.pythonhosted.org/packages/b5/60/6662535354191e2d1555296045b63e4279e5a9dbad49acf55a5d38655a39/torch-2.10.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:aaf663927bcd490ae971469a624c322202a2a1e68936eb952535ca4cd3b90444", size = 915599237, upload-time = "2026-01-21T16:23:25.497Z" }, - { url = "https://files.pythonhosted.org/packages/40/b8/66bbe96f0d79be2b5c697b2e0b187ed792a15c6c4b8904613454651db848/torch-2.10.0-cp310-cp310-win_amd64.whl", hash = "sha256:a4be6a2a190b32ff5c8002a0977a25ea60e64f7ba46b1be37093c141d9c49aeb", size = 113720931, upload-time = "2026-01-21T16:24:23.743Z" }, - { url = "https://files.pythonhosted.org/packages/76/bb/d820f90e69cda6c8169b32a0c6a3ab7b17bf7990b8f2c680077c24a3c14c/torch-2.10.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:35e407430795c8d3edb07a1d711c41cc1f9eaddc8b2f1cc0a165a6767a8fb73d", size = 79411450, upload-time = "2026-01-21T16:25:30.692Z" }, - { url = "https://files.pythonhosted.org/packages/78/89/f5554b13ebd71e05c0b002f95148033e730d3f7067f67423026cc9c69410/torch-2.10.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:3282d9febd1e4e476630a099692b44fdc214ee9bf8ee5377732d9d9dfe5712e4", size = 145992610, upload-time = "2026-01-21T16:25:26.327Z" }, - { url = "https://files.pythonhosted.org/packages/ae/30/a3a2120621bf9c17779b169fc17e3dc29b230c29d0f8222f499f5e159aa8/torch-2.10.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:a2f9edd8dbc99f62bc4dfb78af7bf89499bca3d753423ac1b4e06592e467b763", size = 915607863, upload-time = "2026-01-21T16:25:06.696Z" }, - { url = "https://files.pythonhosted.org/packages/6f/3d/c87b33c5f260a2a8ad68da7147e105f05868c281c63d65ed85aa4da98c66/torch-2.10.0-cp311-cp311-win_amd64.whl", hash = "sha256:29b7009dba4b7a1c960260fc8ac85022c784250af43af9fb0ebafc9883782ebd", size = 113723116, upload-time = "2026-01-21T16:25:21.916Z" }, - { url = "https://files.pythonhosted.org/packages/61/d8/15b9d9d3a6b0c01b883787bd056acbe5cc321090d4b216d3ea89a8fcfdf3/torch-2.10.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:b7bd80f3477b830dd166c707c5b0b82a898e7b16f59a7d9d42778dd058272e8b", size = 79423461, upload-time = "2026-01-21T16:24:50.266Z" }, - { url = "https://files.pythonhosted.org/packages/cc/af/758e242e9102e9988969b5e621d41f36b8f258bb4a099109b7a4b4b50ea4/torch-2.10.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5fd4117d89ffd47e3dcc71e71a22efac24828ad781c7e46aaaf56bf7f2796acf", size = 145996088, upload-time = "2026-01-21T16:24:44.171Z" }, - { url = "https://files.pythonhosted.org/packages/23/8e/3c74db5e53bff7ed9e34c8123e6a8bfef718b2450c35eefab85bb4a7e270/torch-2.10.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:787124e7db3b379d4f1ed54dd12ae7c741c16a4d29b49c0226a89bea50923ffb", size = 915711952, upload-time = "2026-01-21T16:23:53.503Z" }, - { url = "https://files.pythonhosted.org/packages/6e/01/624c4324ca01f66ae4c7cd1b74eb16fb52596dce66dbe51eff95ef9e7a4c/torch-2.10.0-cp312-cp312-win_amd64.whl", hash = "sha256:2c66c61f44c5f903046cc696d088e21062644cbe541c7f1c4eaae88b2ad23547", size = 113757972, upload-time = "2026-01-21T16:24:39.516Z" }, - { url = "https://files.pythonhosted.org/packages/c9/5c/dee910b87c4d5c0fcb41b50839ae04df87c1cfc663cf1b5fca7ea565eeaa/torch-2.10.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:6d3707a61863d1c4d6ebba7be4ca320f42b869ee657e9b2c21c736bf17000294", size = 79498198, upload-time = "2026-01-21T16:24:34.704Z" }, + { url = "https://files.pythonhosted.org/packages/37/81/aa9ab58ec10264c1abe62c8b73f5086c3c558885d6beecebf699f0dbeaeb/torch-2.6.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:6860df13d9911ac158f4c44031609700e1eba07916fff62e21e6ffa0a9e01961", size = 766685561, upload-time = "2025-01-29T16:19:12.12Z" }, + { url = "https://files.pythonhosted.org/packages/86/86/e661e229df2f5bfc6eab4c97deb1286d598bbeff31ab0cdb99b3c0d53c6f/torch-2.6.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c4f103a49830ce4c7561ef4434cc7926e5a5fe4e5eb100c19ab36ea1e2b634ab", size = 95751887, upload-time = "2025-01-29T16:27:50.77Z" }, + { url = "https://files.pythonhosted.org/packages/20/e0/5cb2f8493571f0a5a7273cd7078f191ac252a402b5fb9cb6091f14879109/torch-2.6.0-cp310-cp310-win_amd64.whl", hash = "sha256:56eeaf2ecac90da5d9e35f7f35eb286da82673ec3c582e310a8d1631a1c02341", size = 204165139, upload-time = "2025-01-29T16:27:11.63Z" }, + { url = "https://files.pythonhosted.org/packages/e5/16/ea1b7842413a7b8a5aaa5e99e8eaf3da3183cc3ab345ad025a07ff636301/torch-2.6.0-cp310-none-macosx_11_0_arm64.whl", hash = "sha256:09e06f9949e1a0518c5b09fe95295bc9661f219d9ecb6f9893e5123e10696628", size = 66520221, upload-time = "2025-01-29T16:22:18.862Z" }, + { url = "https://files.pythonhosted.org/packages/78/a9/97cbbc97002fff0de394a2da2cdfa859481fdca36996d7bd845d50aa9d8d/torch-2.6.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:7979834102cd5b7a43cc64e87f2f3b14bd0e1458f06e9f88ffa386d07c7446e1", size = 766715424, upload-time = "2025-01-29T16:25:15.874Z" }, + { url = "https://files.pythonhosted.org/packages/6d/fa/134ce8f8a7ea07f09588c9cc2cea0d69249efab977707cf67669431dcf5c/torch-2.6.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ccbd0320411fe1a3b3fec7b4d3185aa7d0c52adac94480ab024b5c8f74a0bf1d", size = 95759416, upload-time = "2025-01-29T16:27:38.429Z" }, + { url = "https://files.pythonhosted.org/packages/11/c5/2370d96b31eb1841c3a0883a492c15278a6718ccad61bb6a649c80d1d9eb/torch-2.6.0-cp311-cp311-win_amd64.whl", hash = "sha256:46763dcb051180ce1ed23d1891d9b1598e07d051ce4c9d14307029809c4d64f7", size = 204164970, upload-time = "2025-01-29T16:26:16.182Z" }, + { url = "https://files.pythonhosted.org/packages/0b/fa/f33a4148c6fb46ca2a3f8de39c24d473822d5774d652b66ed9b1214da5f7/torch-2.6.0-cp311-none-macosx_11_0_arm64.whl", hash = "sha256:94fc63b3b4bedd327af588696559f68c264440e2503cc9e6954019473d74ae21", size = 66530713, upload-time = "2025-01-29T16:26:38.881Z" }, + { url = "https://files.pythonhosted.org/packages/e5/35/0c52d708144c2deb595cd22819a609f78fdd699b95ff6f0ebcd456e3c7c1/torch-2.6.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:2bb8987f3bb1ef2675897034402373ddfc8f5ef0e156e2d8cfc47cacafdda4a9", size = 766624563, upload-time = "2025-01-29T16:23:19.084Z" }, + { url = "https://files.pythonhosted.org/packages/01/d6/455ab3fbb2c61c71c8842753b566012e1ed111e7a4c82e0e1c20d0c76b62/torch-2.6.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b789069020c5588c70d5c2158ac0aa23fd24a028f34a8b4fcb8fcb4d7efcf5fb", size = 95607867, upload-time = "2025-01-29T16:25:55.649Z" }, + { url = "https://files.pythonhosted.org/packages/18/cf/ae99bd066571656185be0d88ee70abc58467b76f2f7c8bfeb48735a71fe6/torch-2.6.0-cp312-cp312-win_amd64.whl", hash = "sha256:7e1448426d0ba3620408218b50aa6ada88aeae34f7a239ba5431f6c8774b1239", size = 204120469, upload-time = "2025-01-29T16:24:01.821Z" }, + { url = "https://files.pythonhosted.org/packages/81/b4/605ae4173aa37fb5aa14605d100ff31f4f5d49f617928c9f486bb3aaec08/torch-2.6.0-cp312-none-macosx_11_0_arm64.whl", hash = "sha256:9a610afe216a85a8b9bc9f8365ed561535c93e804c2a317ef7fabcc5deda0989", size = 66532538, upload-time = "2025-01-29T16:24:18.976Z" }, +] + +[[package]] +name = "torch-geometric" +version = "2.8.0.post1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "aiohttp" }, + { name = "fsspec" }, + { name = "jinja2" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "psutil" }, + { name = "pyparsing" }, + { name = "requests" }, + { name = "tqdm" }, + { name = "xxhash" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/2c/bfcb404c448f7c347cea7b956d3c7e92d53e36c8db2010eb1bdc1b937bfb/torch_geometric-2.8.0.post1.tar.gz", hash = "sha256:2c0c81666ec10f2132f6b4e0b6c57fc813f2c9f6b57599d7b7a799c614c22fbd", size = 928666, upload-time = "2026-07-20T20:44:40.36Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/14/5c/edf74a71249ad19aa0390fb97ff021e2a5b04ce23252730014e1feac957e/torch_geometric-2.8.0.post1-py3-none-any.whl", hash = "sha256:5d9841cbfa64eadc425e445767127d103dd52fdc5890548c6364986c9bc78029", size = 1325397, upload-time = "2026-07-20T20:44:38.789Z" }, ] [[package]] @@ -8904,7 +9197,7 @@ sdist = { url = "https://files.pythonhosted.org/packages/62/9a/d3d8da1d1a8a189b2 [[package]] name = "torchvision" -version = "0.25.0" +version = "0.21.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, @@ -8913,18 +9206,21 @@ dependencies = [ { name = "torch" }, ] wheels = [ - { url = "https://files.pythonhosted.org/packages/50/ae/cbf727421eb73f1cf907fbe5788326a08f111b3f6b6ddca15426b53fec9a/torchvision-0.25.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a95c47abb817d4e90ea1a8e57bd0d728e3e6b533b3495ae77d84d883c4d11f56", size = 1874919, upload-time = "2026-01-21T16:27:47.617Z" }, - { url = "https://files.pythonhosted.org/packages/64/68/dc7a224f606d53ea09f9a85196a3921ec3a801b0b1d17e84c73392f0c029/torchvision-0.25.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:acc339aba4a858192998c2b91f635827e40d9c469d9cf1455bafdda6e4c28ea4", size = 2343220, upload-time = "2026-01-21T16:27:44.26Z" }, - { url = "https://files.pythonhosted.org/packages/f9/fa/8cce5ca7ffd4da95193232493703d20aa06303f37b119fd23a65df4f239a/torchvision-0.25.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:0d9a3f925a081dd2ebb0b791249b687c2ef2c2717d027946654607494b9b64b6", size = 8068106, upload-time = "2026-01-21T16:27:37.805Z" }, - { url = "https://files.pythonhosted.org/packages/8b/b9/a53bcf8f78f2cd89215e9ded70041765d50ef13bf301f9884ec6041a9421/torchvision-0.25.0-cp310-cp310-win_amd64.whl", hash = "sha256:b57430fbe9e9b697418a395041bb615124d9c007710a2712fda6e35fb310f264", size = 3697295, upload-time = "2026-01-21T16:27:36.574Z" }, - { url = "https://files.pythonhosted.org/packages/3e/be/c704bceaf11c4f6b19d64337a34a877fcdfe3bd68160a8c9ae9bea4a35a3/torchvision-0.25.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:db74a551946b75d19f9996c419a799ffdf6a223ecf17c656f90da011f1d75b20", size = 1874923, upload-time = "2026-01-21T16:27:46.574Z" }, - { url = "https://files.pythonhosted.org/packages/ae/e9/f143cd71232430de1f547ceab840f68c55e127d72558b1061a71d0b193cd/torchvision-0.25.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:f49964f96644dbac2506dffe1a0a7ec0f2bf8cf7a588c3319fed26e6329ffdf3", size = 2344808, upload-time = "2026-01-21T16:27:43.191Z" }, - { url = "https://files.pythonhosted.org/packages/43/ae/ad5d6165797de234c9658752acb4fce65b78a6a18d82efdf8367c940d8da/torchvision-0.25.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:153c0d2cbc34b7cf2da19d73450f24ba36d2b75ec9211b9962b5022fb9e4ecee", size = 8070752, upload-time = "2026-01-21T16:27:33.748Z" }, - { url = "https://files.pythonhosted.org/packages/23/19/55b28aecdc7f38df57b8eb55eb0b14a62b470ed8efeb22cdc74224df1d6a/torchvision-0.25.0-cp311-cp311-win_amd64.whl", hash = "sha256:ea580ffd6094cc01914ad32f8c8118174f18974629af905cea08cb6d5d48c7b7", size = 4038722, upload-time = "2026-01-21T16:27:41.355Z" }, - { url = "https://files.pythonhosted.org/packages/56/3a/6ea0d73f49a9bef38a1b3a92e8dd455cea58470985d25635beab93841748/torchvision-0.25.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c2abe430c90b1d5e552680037d68da4eb80a5852ebb1c811b2b89d299b10573b", size = 1874920, upload-time = "2026-01-21T16:27:45.348Z" }, - { url = "https://files.pythonhosted.org/packages/51/f8/c0e1ef27c66e15406fece94930e7d6feee4cb6374bbc02d945a630d6426e/torchvision-0.25.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:b75deafa2dfea3e2c2a525559b04783515e3463f6e830cb71de0fb7ea36fe233", size = 2344556, upload-time = "2026-01-21T16:27:40.125Z" }, - { url = "https://files.pythonhosted.org/packages/68/2f/f24b039169db474e8688f649377de082a965fbf85daf4e46c44412f1d15a/torchvision-0.25.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:f25aa9e380865b11ea6e9d99d84df86b9cc959f1a007cd966fc6f1ab2ed0e248", size = 8072351, upload-time = "2026-01-21T16:27:21.074Z" }, - { url = "https://files.pythonhosted.org/packages/ad/16/8f650c2e288977cf0f8f85184b90ee56ed170a4919347fc74ee99286ed6f/torchvision-0.25.0-cp312-cp312-win_amd64.whl", hash = "sha256:f9c55ae8d673ab493325d1267cbd285bb94d56f99626c00ac4644de32a59ede3", size = 4303059, upload-time = "2026-01-21T16:27:11.08Z" }, + { url = "https://files.pythonhosted.org/packages/a9/20/72eb0b5b08fa293f20fc41c374e37cf899f0033076f0144d2cdc48f9faee/torchvision-0.21.0-1-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:5568c5a1ff1b2ec33127b629403adb530fab81378d9018ca4ed6508293f76e2b", size = 2327643, upload-time = "2025-03-18T17:25:51.165Z" }, + { url = "https://files.pythonhosted.org/packages/4e/3d/b7241abfa3e6651c6e00796f5de2bd1ce4d500bf5159bcbfeea47e711b93/torchvision-0.21.0-1-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:ff96666b94a55e802ea6796cabe788541719e6f4905fc59c380fed3517b6a64d", size = 2329320, upload-time = "2025-03-18T17:25:52.272Z" }, + { url = "https://files.pythonhosted.org/packages/52/5b/76ca113a853b19c7b1da761f8a72cb6429b3bd0bf932537d8df4657f47c3/torchvision-0.21.0-1-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ffa2a16499508fe6798323e455f312c7c55f2a88901c9a7c0fb1efa86cf7e327", size = 2329878, upload-time = "2025-03-18T17:25:50.039Z" }, + { url = "https://files.pythonhosted.org/packages/8e/0d/143bd264876fad17c82096b6c2d433f1ac9b29cdc69ee45023096976ee3d/torchvision-0.21.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:044ea420b8c6c3162a234cada8e2025b9076fa82504758cd11ec5d0f8cd9fa37", size = 1784140, upload-time = "2025-01-29T16:28:54.122Z" }, + { url = "https://files.pythonhosted.org/packages/5e/44/32e2d2d174391374d5ff3c4691b802e8efda9ae27ab9062eca2255b006af/torchvision-0.21.0-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:b0c0b264b89ab572888244f2e0bad5b7eaf5b696068fc0b93e96f7c3c198953f", size = 7237187, upload-time = "2025-01-29T16:28:47.156Z" }, + { url = "https://files.pythonhosted.org/packages/0e/6b/4fca9373eda42c1b04096758306b7bd55f7d8f78ba273446490855a0f25d/torchvision-0.21.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:54815e0a56dde95cc6ec952577f67e0dc151eadd928e8d9f6a7f821d69a4a734", size = 14699067, upload-time = "2025-01-29T16:28:36.086Z" }, + { url = "https://files.pythonhosted.org/packages/aa/f7/799ddd538b21017cbf80294c92e9efbf6db08dff6efee37c3be114a81845/torchvision-0.21.0-cp310-cp310-win_amd64.whl", hash = "sha256:abbf1d7b9d52c00d2af4afa8dac1fb3e2356f662a4566bd98dfaaa3634f4eb34", size = 1560542, upload-time = "2025-01-29T16:28:52.608Z" }, + { url = "https://files.pythonhosted.org/packages/29/88/00c69db213ee2443ada8886ec60789b227e06bb869d85ee324578221a7f7/torchvision-0.21.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:110d115333524d60e9e474d53c7d20f096dbd8a080232f88dddb90566f90064c", size = 1784141, upload-time = "2025-01-29T16:28:51.207Z" }, + { url = "https://files.pythonhosted.org/packages/be/a2/b0cedf0a411f1a5d75cfc0b87cde56dd1ddc1878be46a42c905cd8580220/torchvision-0.21.0-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:3891cd086c5071bda6b4ee9d266bb2ac39c998c045c2ebcd1e818b8316fb5d41", size = 7237719, upload-time = "2025-01-29T16:28:20.724Z" }, + { url = "https://files.pythonhosted.org/packages/8c/a1/ee962ef9d0b2bf7a6f8b14cb95acb70e05cd2101af521032a09e43f8582f/torchvision-0.21.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:54454923a50104c66a9ab6bd8b73a11c2fc218c964b1006d5d1fe5b442c3dcb6", size = 14700617, upload-time = "2025-01-29T16:28:30.247Z" }, + { url = "https://files.pythonhosted.org/packages/88/53/4ad334b9b1d8dd99836869fec139cb74a27781298360b91b9506c53f1d10/torchvision-0.21.0-cp311-cp311-win_amd64.whl", hash = "sha256:49bcfad8cfe2c27dee116c45d4f866d7974bcf14a5a9fbef893635deae322f2f", size = 1560523, upload-time = "2025-01-29T16:28:48.751Z" }, + { url = "https://files.pythonhosted.org/packages/6e/1b/28f527b22d5e8800184d0bc847f801ae92c7573a8c15979d92b7091c0751/torchvision-0.21.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:97a5814a93c793aaf0179cfc7f916024f4b63218929aee977b645633d074a49f", size = 1784140, upload-time = "2025-01-29T16:28:44.694Z" }, + { url = "https://files.pythonhosted.org/packages/36/63/0722e153fd27d64d5b0af45b5c8cb0e80b35a68cf0130303bc9a8bb095c7/torchvision-0.21.0-cp312-cp312-manylinux1_x86_64.whl", hash = "sha256:b578bcad8a4083b40d34f689b19ca9f7c63e511758d806510ea03c29ac568f7b", size = 7238673, upload-time = "2025-01-29T16:28:27.631Z" }, + { url = "https://files.pythonhosted.org/packages/bb/ea/03541ed901cdc30b934f897060d09bbf7a98466a08ad1680320f9ce0cbe0/torchvision-0.21.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:5083a5b1fec2351bf5ea9900a741d54086db75baec4b1d21e39451e00977f1b1", size = 14701186, upload-time = "2025-01-29T16:28:16.491Z" }, + { url = "https://files.pythonhosted.org/packages/4c/6a/c7752603060d076dfed95135b78b047dc71792630cbcb022e3693d6f32ef/torchvision-0.21.0-cp312-cp312-win_amd64.whl", hash = "sha256:6eb75d41e3bbfc2f7642d0abba9383cc9ae6c5a4ca8d6b00628c225e1eaa63b3", size = 1560520, upload-time = "2025-01-29T16:28:42.122Z" }, ] [[package]] @@ -9007,6 +9303,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/43/2b/36e984399089c026a6499ac8f7401d38487cf0183839a4aa78140d373771/treescope-0.1.10-py3-none-any.whl", hash = "sha256:dde52f5314f4c29d22157a6fe4d3bd103f9cae02791c9e672eefa32c9aa1da51", size = 182255, upload-time = "2025-08-08T05:43:46.673Z" }, ] +[[package]] +name = "triangle" +version = "20250106" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/55/44/c5b03ff3d806ea05f58e072919c1b002513fab3db17125e14ad70687edd9/triangle-20250106-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:5eb99ec1002f164b7a40bec47c9915818231c3563f3900d973b6a2cb2befef9d", size = 1438057, upload-time = "2025-01-07T04:53:58.112Z" }, + { url = "https://files.pythonhosted.org/packages/ef/8f/8585f9fa048ca4541e1b97562bd5518731af73a84739e852c27a9c58478f/triangle-20250106-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d32c36a7188234a225a41f98224e430eae48ba252942696f2a7c83693a41fd0f", size = 2059234, upload-time = "2025-01-07T04:55:58.791Z" }, + { url = "https://files.pythonhosted.org/packages/ed/f2/a5e0882f3a65f256f925b3034e0dfa1a40a6b9b70fb52cd2d04507b372ba/triangle-20250106-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e08357d66a48d8742d25479f55478911750513f4d3875e836d5d6cc0ba9dbda1", size = 1985587, upload-time = "2025-01-07T04:56:01.34Z" }, + { url = "https://files.pythonhosted.org/packages/45/ac/cad720cd3bbdd30c91529556ca1616fc34d4b94e9cd50bff15d9dd7bad2f/triangle-20250106-cp310-cp310-win32.whl", hash = "sha256:11a0b634af43789e4525f2f4247dda83043f9fdfd710b55bcb82b514471ab6d7", size = 1406838, upload-time = "2025-01-07T05:00:05.181Z" }, + { url = "https://files.pythonhosted.org/packages/e6/82/01827a6c872f7588f4252c05a542764f561c7cadcd564a4000e8afd2d33f/triangle-20250106-cp310-cp310-win_amd64.whl", hash = "sha256:8a31f67e3506cc3aa33f82f1f94ea365b9625a7737fa119d628580c8bac0b0bf", size = 1425912, upload-time = "2025-01-07T05:00:07.885Z" }, + { url = "https://files.pythonhosted.org/packages/5c/91/83167e3d0cd1912b46e61a2b3485bf9886e49b48cea4daa59e8de4058a1a/triangle-20250106-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:93bd02b3341b2e4452952cb7605cf239bddd788a6f410ea54aeeb7db61836ba5", size = 1438115, upload-time = "2025-01-07T04:54:01.302Z" }, + { url = "https://files.pythonhosted.org/packages/8b/de/2383f2cd1dcc93a7042fb4feedb551c29cc40a3dffdc00b0387a2c591456/triangle-20250106-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80da149e4584cc599fc9bcff31dddd524528a41119f5af321911e599d82ad6e1", size = 2100052, upload-time = "2025-01-07T04:56:03.954Z" }, + { url = "https://files.pythonhosted.org/packages/ae/d9/30504df67bb5451097ad4cb67f558be41ed7bf780f300bae1fc7cb90ee05/triangle-20250106-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c1a22e82701fe97c0cbfe9ecbfc394a4a36442f1fe2836a721ddb95be9cdb299", size = 2028993, upload-time = "2025-01-07T04:56:06.767Z" }, + { url = "https://files.pythonhosted.org/packages/f7/4b/b8b6e97452bbadb9d388a132c78b0b4e1eb9e61ffc7c3c8b4992342327ed/triangle-20250106-cp311-cp311-win32.whl", hash = "sha256:3097f96859c02c9e3f5a2c35b0016b28005e7e6f061059b9c7a412e2ad01eb86", size = 1406419, upload-time = "2025-01-07T05:00:10.279Z" }, + { url = "https://files.pythonhosted.org/packages/0d/18/2a5fe89c7b98501d1dbb267119a3ce993ab96f6f11abf9e879dbbe3297ac/triangle-20250106-cp311-cp311-win_amd64.whl", hash = "sha256:cbd195668be437cfdc8cf192cec5748ebbb5d3bff19201f8503495916dd54ded", size = 1425906, upload-time = "2025-01-07T05:00:12.201Z" }, + { url = "https://files.pythonhosted.org/packages/6f/ba/22c552b21aa5a7724e712372d29c9397db19086e99c62f876c1b73025df2/triangle-20250106-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:64106544f6d137b619f7f724abbb7c2c78353691cccfc163e9d0b2e2476e0853", size = 1439851, upload-time = "2025-01-07T04:54:03.857Z" }, + { url = "https://files.pythonhosted.org/packages/fa/93/ce4d0c46ff570993f4302ce55300dd310b7c957a8e66890ed00691229f5b/triangle-20250106-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:38c221bb6e403f81de49050899502a8326c9565f62c4d69171070b41dc25cb69", size = 2089547, upload-time = "2025-01-07T04:56:09.463Z" }, + { url = "https://files.pythonhosted.org/packages/23/e0/bd0a7e624fc5fc8636d0ad281c5b0624027dc1855218ce6a251c581d7127/triangle-20250106-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b582b342cddb660dffc6bd7d7233f66952d2a3d18ccbc097660a8e370ee38d0c", size = 2009167, upload-time = "2025-01-07T04:56:12.221Z" }, + { url = "https://files.pythonhosted.org/packages/26/3f/7c79202ec374bd122b63250d768be34674043be9b97f6bb8c115df64e880/triangle-20250106-cp312-cp312-win32.whl", hash = "sha256:9532797a15687225a0ee67619ad6f3baeb72bf193541eb96f782e41c577cc81b", size = 1407116, upload-time = "2025-01-07T05:00:13.664Z" }, + { url = "https://files.pythonhosted.org/packages/a1/a5/4a09c3f9d2687d8752c912a97f2c5086cdd83721b3b13f8288f13b771fa7/triangle-20250106-cp312-cp312-win_amd64.whl", hash = "sha256:0327032a7984a7262180ef2ddd78b36dfdcdecbc79f0f9f173732ce7c670b8ed", size = 1426720, upload-time = "2025-01-07T05:00:17.789Z" }, +] + [[package]] name = "trimesh" version = "4.12.2" @@ -9020,40 +9342,32 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/05/98/716a473cfb24750858ddd5d14e6527539dd206583a46408d08eeb2844a75/trimesh-4.12.2-py3-none-any.whl", hash = "sha256:b5b5afa63c5272345f2858f7676bc8c217dc8a89f4fadf6193fe10a81b5ff2aa", size = 741043, upload-time = "2026-05-01T00:57:40.763Z" }, ] -[package.optional-dependencies] -easy = [ - { name = "charset-normalizer" }, - { name = "colorlog" }, - { name = "embreex" }, - { name = "httpx" }, - { name = "jsonschema" }, - { name = "lxml" }, - { name = "manifold3d" }, - { name = "mapbox-earcut" }, - { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "pillow" }, - { name = "pycollada" }, - { name = "rtree" }, - { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "scipy", version = "1.17.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, - { name = "shapely" }, - { name = "svg-path" }, - { name = "vhacdx" }, - { name = "xxhash" }, +[[package]] +name = "triton" +version = "3.2.0" +source = { registry = "https://pypi.org/simple" } +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'", + "python_full_version < '3.11' and platform_machine != 'aarch64' and sys_platform != 'darwin' and sys_platform != 'win32'", +] +wheels = [ + { url = "https://files.pythonhosted.org/packages/01/65/3ffa90e158a2c82f0716eee8d26a725d241549b7d7aaf7e4f44ac03ebd89/triton-3.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b3e54983cd51875855da7c68ec05c05cf8bb08df361b1d5b69e05e40b0c9bd62", size = 253090354, upload-time = "2025-01-22T19:12:21.872Z" }, + { url = "https://files.pythonhosted.org/packages/a7/2e/757d2280d4fefe7d33af7615124e7e298ae7b8e3bc4446cdb8e88b0f9bab/triton-3.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8009a1fb093ee8546495e96731336a33fb8856a38e45bb4ab6affd6dbc3ba220", size = 253157636, upload-time = "2025-01-22T19:12:51.322Z" }, + { url = "https://files.pythonhosted.org/packages/06/00/59500052cb1cf8cf5316be93598946bc451f14072c6ff256904428eaf03c/triton-3.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8d9b215efc1c26fa7eefb9a157915c92d52e000d2bf83e5f69704047e63f125c", size = 253159365, upload-time = "2025-01-22T19:13:24.648Z" }, ] [[package]] name = "triton" version = "3.6.0" source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "importlib-metadata", marker = "(platform_machine != 'aarch64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32')" }, +resolution-markers = [ + "python_full_version >= '3.12' and platform_machine == 'aarch64' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version == '3.11.*' and platform_machine == 'aarch64' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", + "python_full_version < '3.11' and platform_machine == 'aarch64' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'", ] -wheels = [ - { url = "https://files.pythonhosted.org/packages/8c/f7/f1c9d3424ab199ac53c2da567b859bcddbb9c9e7154805119f8bd95ec36f/triton-3.6.0-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a6550fae429e0667e397e5de64b332d1e5695b73650ee75a6146e2e902770bea", size = 188105201, upload-time = "2026-01-20T16:00:29.272Z" }, - { url = "https://files.pythonhosted.org/packages/e0/12/b05ba554d2c623bffa59922b94b0775673de251f468a9609bc9e45de95e9/triton-3.6.0-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:e8e323d608e3a9bfcc2d9efcc90ceefb764a82b99dea12a86d643c72539ad5d3", size = 188214640, upload-time = "2026-01-20T16:00:35.869Z" }, - { url = "https://files.pythonhosted.org/packages/ab/a8/cdf8b3e4c98132f965f88c2313a4b493266832ad47fb52f23d14d4f86bb5/triton-3.6.0-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:74caf5e34b66d9f3a429af689c1c7128daba1d8208df60e81106b115c00d6fca", size = 188266850, upload-time = "2026-01-20T16:00:43.041Z" }, +dependencies = [ + { name = "importlib-metadata", marker = "platform_machine == 'aarch64' and sys_platform != 'darwin' and sys_platform != 'linux' and sys_platform != 'win32'" }, ] [[package]] @@ -9297,6 +9611,24 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/09/ef/697ccbf91833155fa36ee0919918eb02df49a47005d1ff8a78ca13e09893/unitree_webrtc_connect-2.1.2-py3-none-any.whl", hash = "sha256:230bcbc5cc39f0dd621cdebf0568e96274c3531e232611293f8182af9e827d80", size = 52315, upload-time = "2026-05-17T22:06:40.924Z" }, ] +[[package]] +name = "urdfpy" +version = "0.0.4" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "lxml" }, + { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pillow" }, + { name = "pycollada" }, + { name = "pyrender" }, + { name = "six" }, + { name = "trimesh" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/42/8d/3e64966a55afe0214f2f006730dad7c615828117b0c56de0b8819c6575bc/urdfpy-0.0.4.tar.gz", hash = "sha256:5bae6e06572b72426565bcca2106de712a18cfb91a6b00c159451263e72a5839", size = 20777, upload-time = "2019-03-03T23:23:04.779Z" } + [[package]] name = "urllib3" version = "2.6.3" @@ -9403,36 +9735,17 @@ wheels = [ ] [[package]] -name = "vhacdx" -version = "0.0.10" +name = "vcs-versioning" +version = "2.2.2" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "packaging" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/15/e3/d2abc3dc4c1cb216c2efdc70b36f80efeb1bdbd7d420a676ddc9d9d980e1/vhacdx-0.0.10.tar.gz", hash = "sha256:fcc23201e319d79fe25e064847efc254bd39ac30af28cc761409e1f9142dd033", size = 58125, upload-time = "2025-12-02T20:58:45.358Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c4/f4/da308d86daaa9c636851357cbd928715d47963beecd525b3749d2d5c9537/vhacdx-0.0.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4bc7be82fab608cb7231e95a0a10700be1e9422a36b21e7d49c782a598c8d37c", size = 222760, upload-time = "2025-12-02T20:57:30.778Z" }, - { url = "https://files.pythonhosted.org/packages/e0/8a/e3462a43ec6712b74d921e4af9d5a2998752378c5554bde9a594dbb0cf0c/vhacdx-0.0.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:4b63d1c5ad0e64c300a3a9d9404f4778df367b8c545639dfb932db4b76704ff3", size = 208812, upload-time = "2025-12-02T20:57:33.486Z" }, - { url = "https://files.pythonhosted.org/packages/fb/d1/b717275adb108431f1404193542fab7ecf4c5bae221f1552bbd570fe0e5d/vhacdx-0.0.10-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9bcf3fe1c555598e41348108b55a0fc67534e7fef2367452c301014518c1476", size = 236999, upload-time = "2025-12-02T20:57:34.971Z" }, - { url = "https://files.pythonhosted.org/packages/bf/84/97e2305f6bd4a4de3d40bb234c38282cbcf2fa30653ff5ae4f7df9d8f3ec/vhacdx-0.0.10-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:9506ca89289da63e5a3d1ac97aa7413aece47d65cbaa4b0c409469555add0e06", size = 250035, upload-time = "2025-12-02T20:57:36.037Z" }, - { url = "https://files.pythonhosted.org/packages/9d/66/eb1d8d64742b9e73557e075cea6ee7e4976dd89b84c7d3197ca3621d5a85/vhacdx-0.0.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:06faf9caa0abceddd5fa505e4299e2ebf14bc26c58a1e521013717cbf37bea61", size = 1224134, upload-time = "2025-12-02T20:57:37.217Z" }, - { url = "https://files.pythonhosted.org/packages/47/db/e829b21b071db94f45079c4ace2f967c684f08b10ea285919a95e9d5fe21/vhacdx-0.0.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3a6b43b42290697e2bd04087977d1e3841d287c528e414c765581ecec62e66f6", size = 1284300, upload-time = "2025-12-02T20:57:38.78Z" }, - { url = "https://files.pythonhosted.org/packages/ff/aa/b401565542b927ce3e0a6d5e72acef79343a449ee1a7ad94a5c7266bab26/vhacdx-0.0.10-cp310-cp310-win_amd64.whl", hash = "sha256:27eb3b293ccef1332d477346d564bb4c474bb451e9b753e3ce9cac01cbb90a0c", size = 193069, upload-time = "2025-12-02T20:57:40.318Z" }, - { url = "https://files.pythonhosted.org/packages/b7/2c/d49df6fec3294cef3c8c88c54784162bd8350c427fecd9b16335772b760f/vhacdx-0.0.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8584f33ed020b6cce678b8febcf84af22bced617ef31c85bf31fd7e2b4bba9fe", size = 224113, upload-time = "2025-12-02T20:57:41.59Z" }, - { url = "https://files.pythonhosted.org/packages/68/1d/bd2456baa6b16977c106adc2386b6e7a34c3e57ade6aeeab68bb61ceb16f/vhacdx-0.0.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:a9b63cdb5f34dfee386b64a01f7e1571ef0c2555244ea3d83a09d78273123bce", size = 210118, upload-time = "2025-12-02T20:57:42.749Z" }, - { url = "https://files.pythonhosted.org/packages/49/ab/15adb78489b51c2a898642755be727ecd7c3de37cac6e434ce420b8ce27c/vhacdx-0.0.10-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:915eab6c19fdf63ab256855331db546575284786a480aa2d67437db0e86b0d17", size = 238276, upload-time = "2025-12-02T20:57:43.95Z" }, - { url = "https://files.pythonhosted.org/packages/a6/f1/464c761dbe24f58d6fc354bf51729342981fb7a621e170e0d3512fadbec8/vhacdx-0.0.10-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e335bb9af540e6867ff051166a399075823fdd8fc1fc27e9530995cc1bda1eb", size = 251383, upload-time = "2025-12-02T20:57:45.246Z" }, - { url = "https://files.pythonhosted.org/packages/b2/22/c7b4117c5431189a6a019e8fc2cf590df3ab196c38b4b7c3622292205d9b/vhacdx-0.0.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c3ddbaa38eb65c3aec9b0e39a822223474c931c0e18d3e93a3a499870ffa45ad", size = 1225200, upload-time = "2025-12-02T20:57:46.639Z" }, - { url = "https://files.pythonhosted.org/packages/6c/62/c679ad28ce7854771913255e1abc588b3643c2147fb5c51a8553224aa1dd/vhacdx-0.0.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:d398fcc13e330ed1fd2540a7d572aeca0be9621411def78e10c7ea4132f959ee", size = 1285935, upload-time = "2025-12-02T20:57:48.51Z" }, - { url = "https://files.pythonhosted.org/packages/de/c8/a8260b780e4578d7ef19b70343f9717f74ff48f9950138c96c78f209ec01/vhacdx-0.0.10-cp311-cp311-win_amd64.whl", hash = "sha256:c9665a3ef887babcac8b5822f01288e8f06b4a949fadbbe1861670b358f111ee", size = 194137, upload-time = "2025-12-02T20:57:50.207Z" }, - { url = "https://files.pythonhosted.org/packages/cf/9c/66375e65634c80f6efb46e81915126bf3e55dc9d6615217590cbc8316d2e/vhacdx-0.0.10-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7dd17d697d6d4d7cf66f1e947e0530041913981e05f7025236bec28a350b1a33", size = 224998, upload-time = "2025-12-02T20:57:51.639Z" }, - { url = "https://files.pythonhosted.org/packages/4e/e3/fc2644d3e7d0b2b52e2f681eb2878c0e1b9cafc53946f66736d0f01e237c/vhacdx-0.0.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:189ded39b709436cb732cdf694d4cf22e877aefb97e2ab2b55bf7ada9c030f93", size = 211130, upload-time = "2025-12-02T20:57:53.018Z" }, - { url = "https://files.pythonhosted.org/packages/e3/93/0b0f1977f5b3c2e1bbea5ef85e37a808ff73f1b7daf42950c57090e90dc7/vhacdx-0.0.10-cp312-cp312-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f3b03d35ab56a93beee338175dbe0b87552353e5dfb3ff37467e88f56cedf7cc", size = 239661, upload-time = "2025-12-02T20:57:54.144Z" }, - { url = "https://files.pythonhosted.org/packages/94/98/d2a6aeb1c6570a1fc1be29ee03db795f643ab03c6df7635522f23796b39d/vhacdx-0.0.10-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ea8c54ed193fa0db0248928fbf5d438b3872d615a506889d5b89fc6467d6411a", size = 252938, upload-time = "2025-12-02T20:57:55.275Z" }, - { url = "https://files.pythonhosted.org/packages/94/2e/1e678efc161a0d7fe1806f5e037ce11cc5964db7e08ccfc220ef63951863/vhacdx-0.0.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:a5c898104140c72e4dc789e6125812671eee5e412916e83eff24a6148248ff5e", size = 1226696, upload-time = "2025-12-02T20:57:56.438Z" }, - { url = "https://files.pythonhosted.org/packages/90/5b/b302a0420a241c4910f4870eb9f39e6ada59858db441cc35bda511c17982/vhacdx-0.0.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:abdd0ba17786e578206594731df15c90e2751b6884220c8673124f47fd7ac620", size = 1287794, upload-time = "2025-12-02T20:57:57.694Z" }, - { url = "https://files.pythonhosted.org/packages/73/e9/f9729603ac75047a257f1b4ddac60cbde72b0abfd49ffed305751ba630a2/vhacdx-0.0.10-cp312-cp312-win_amd64.whl", hash = "sha256:79e7db59b4042295b21b79d55ba486a9a480550f696d466f158a30ed920dd0ec", size = 195033, upload-time = "2025-12-02T20:57:58.95Z" }, +sdist = { url = "https://files.pythonhosted.org/packages/09/95/c95bb74950763a163defcf4cedf6c5edfca1d623fd5031b76516ece85076/vcs_versioning-2.2.2.tar.gz", hash = "sha256:4ac4ded78720cdb4d0291ae58ace87e1e9201912e1023f3029c6cce5c9152cfb", size = 143135, upload-time = "2026-06-29T13:26:06.901Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/85/9e/1f06f4ddc3a74ccfe0877490caaf4a5233e65401160afc8cb13207815f5e/vcs_versioning-2.2.2-py3-none-any.whl", hash = "sha256:fe7fb216f8780a5516e7864a9333aacb1b70015b087a9be3918da76ef2760808", size = 108014, upload-time = "2026-06-29T13:26:05.367Z" }, ] [[package]] @@ -9615,6 +9928,21 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/68/5a/199c59e0a824a3db2b89c5d2dade7ab5f9624dbf6448dc291b46d5ec94d3/wcwidth-0.6.0-py3-none-any.whl", hash = "sha256:1a3a1e510b553315f8e146c54764f4fb6264ffad731b3d78088cdb1478ffbdad", size = 94189, upload-time = "2026-02-06T19:19:39.646Z" }, ] +[[package]] +name = "webdataset" +version = "1.0.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "braceexpand" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "pyyaml" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5a/3a/68800d92e065cf4750ebecf973b13979c0c929b439e1293012938862038d/webdataset-1.0.2.tar.gz", hash = "sha256:7f0498be827cfa46cc5430a58768a24e2c6a410676a61be1838f53d61afdaab4", size = 80090, upload-time = "2025-06-19T23:26:21.945Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/d9/00/aca6beb3658dab4ed3dbb41a78e6e7f31342e0b41d28088f205525751601/webdataset-1.0.2-py3-none-any.whl", hash = "sha256:3dbfced32b25c0d199c6b9787937b6f85742bc3c84f652c846893075c1c082d9", size = 74956, upload-time = "2025-06-19T23:26:20.354Z" }, +] + [[package]] name = "websocket-client" version = "1.9.0" @@ -9998,16 +10326,16 @@ wheels = [ [[package]] name = "yapf" -version = "0.40.2" +version = "0.40.1" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "importlib-metadata" }, { name = "platformdirs" }, { name = "tomli" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/b9/14/c1f0ebd083fddd38a7c832d5ffde343150bd465689d12c549c303fbcd0f5/yapf-0.40.2.tar.gz", hash = "sha256:4dab8a5ed7134e26d57c1647c7483afb3f136878b579062b786c9ba16b94637b", size = 252068, upload-time = "2023-09-22T18:40:46.232Z" } +sdist = { url = "https://files.pythonhosted.org/packages/e0/7a/9020bfa17d294b5d0d8bf26bb175ad4c90d1e3ad4039001f621ef046cb06/yapf-0.40.1.tar.gz", hash = "sha256:958587eb5c8ec6c860119a9c25d02addf30a44f75aa152a4220d30e56a98037c", size = 247509, upload-time = "2023-06-20T05:43:26.352Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/66/c9/d4b03b2490107f13ebd68fe9496d41ae41a7de6275ead56d0d4621b11ffd/yapf-0.40.2-py3-none-any.whl", hash = "sha256:adc8b5dd02c0143108878c499284205adb258aad6db6634e5b869e7ee2bd548b", size = 254707, upload-time = "2023-09-22T18:40:43.297Z" }, + { url = "https://files.pythonhosted.org/packages/23/75/c374517c09e31bf22d3b3f156d73e0f38d08e29b2afdd607cef5f1e10aa9/yapf-0.40.1-py3-none-any.whl", hash = "sha256:b8bfc1f280949153e795181768ca14ef43d7312629a06c43e7abd279323af313", size = 250316, upload-time = "2023-06-20T05:43:23.68Z" }, ] [[package]] @@ -10085,7 +10413,7 @@ dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.3.5", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "six" }, - { name = "trimesh", extra = ["easy"] }, + { name = "trimesh" }, ] sdist = { url = "https://files.pythonhosted.org/packages/ff/19/20c50861f30aff7720f9a601f386d73760c2df9961de1f98d0dbf3b85e69/yourdfpy-0.0.60.tar.gz", hash = "sha256:2af2d8bdeea1b85b642590a3b4236fdb35746d7b3e38ce460a169c18d9c4f868", size = 538238, upload-time = "2026-01-23T07:32:47.856Z" } wheels = [