Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions source/matterix_assets/matterix_assets/labware/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@
##

from .beakers import *
from .vials import *
from .vialplates import *
49 changes: 49 additions & 0 deletions source/matterix_assets/matterix_assets/labware/vialplates.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Copyright (c) 2022-2026, The Matterix Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Configuration and data paths for the promoted 3_5 vial holder."""

import json
from pathlib import Path

from isaaclab.sensors import OffsetCfg
from isaaclab.utils import configclass

from matterix_assets import MATTERIX_ASSETS_DATA_DIR

from ..matterix_rigid_object import MatterixRigidObjectCfg

VIALPLATE_3_5_DATA_DIR = f"{MATTERIX_ASSETS_DATA_DIR}/labware/vialplate_3_5"
VIALPLATE_3_5_USD_PATH = f"{VIALPLATE_3_5_DATA_DIR}/3_5_vialplate_free_standing_frames.usda"
VIALPLATE_3_5_FRAME_CONTRACT_PATH = f"{VIALPLATE_3_5_DATA_DIR}/holder-hole-frame-contract.json"


def _load_holder_hole_frames() -> dict[str, OffsetCfg]:
"""Load the public 15-hole frame family from the promoted contract."""
contract = json.loads(Path(VIALPLATE_3_5_FRAME_CONTRACT_PATH).read_text(encoding="utf-8"))
frames = contract.get("frames")
if not isinstance(frames, list) or len(frames) != 15:
raise ValueError("promoted vial-holder frame contract must contain exactly 15 frames")
orientation = tuple(float(value) for value in contract["frame_orientation_wxyz"])
return {
frame["name"]: OffsetCfg(pos=tuple(float(value) for value in frame["position_m"]), rot=orientation)
for frame in frames
}


VIALPLATE_3_5_HOLE_FRAMES = _load_holder_hole_frames()


@configclass
class VIALPLATE_3_5_CFG(MatterixRigidObjectCfg):
"""Dynamic 15-well holder with the promoted frame-bearing USD payload."""

prim_path = "{ENV_REGEX_NS}/RigidObjects_Labware"
usd_path = VIALPLATE_3_5_USD_PATH
scale = (1.0, 1.0, 1.0)
mass = 0.070350472
activate_contact_sensors = True
frames = VIALPLATE_3_5_HOLE_FRAMES
semantic_tags = [("class", "vial_holder"), ("asset", "3_5_vialplate")]
37 changes: 37 additions & 0 deletions source/matterix_assets/matterix_assets/labware/vials.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Copyright (c) 2022-2026, The Matterix Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause
# Configuration for the self-modelled Fisherbrand 03-339-21F reference vial.

from isaaclab.sensors import OffsetCfg
from isaaclab.utils import configclass

from matterix_assets import MATTERIX_ASSETS_DATA_DIR

from ..matterix_rigid_object import MatterixRigidObjectCfg

FISHERBRAND_03_339_21F_DATA_DIR = (
f"{MATTERIX_ASSETS_DATA_DIR}/labware/fisherbrand_03-339-21f"
)
FISHERBRAND_03_339_21F_USD_PATH = (
f"{FISHERBRAND_03_339_21F_DATA_DIR}/fisherbrand_03-339-21f_z_up_fixed.usda"
)
FISHERBRAND_03_339_21F_FRAME_OFFSETS = {
"grasp": OffsetCfg(pos=(0.0, 0.0, 0.0649)),
"pre_grasp": OffsetCfg(pos=(0.0, 0.0, 0.1649)),
"post_grasp": OffsetCfg(pos=(0.0, 0.0, 0.1649)),
}


@configclass
class FISHERBRAND_03_339_21F_CFG(MatterixRigidObjectCfg):
# Dynamic closed Fisherbrand 03-339-21F vial configuration.

prim_path = "{ENV_REGEX_NS}/RigidObjects_Labware"
usd_path = FISHERBRAND_03_339_21F_USD_PATH
scale = (1.0, 1.0, 1.0)
mass = 0.017734
activate_contact_sensors = True
frames = FISHERBRAND_03_339_21F_FRAME_OFFSETS
semantic_tags = [("class", "vial"), ("asset", "fisherbrand_03-339-21f")]
75 changes: 75 additions & 0 deletions source/matterix_assets/test/test_promoted_vialplate_assets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Copyright (c) 2022-2026, The Matterix Project Developers.
# All rights reserved.
#
# SPDX-License-Identifier: BSD-3-Clause

"""Static checks for the promoted vial and holder payloads."""

import hashlib
import json
from pathlib import Path


MATTERIX_ROOT = Path(__file__).resolve().parents[3]
DATA_ROOT = MATTERIX_ROOT / "source/matterix_assets/data/labware"
VIAL_ROOT = DATA_ROOT / "fisherbrand_03-339-21f"
HOLDER_ROOT = DATA_ROOT / "vialplate_3_5"
TASK_PATH = MATTERIX_ROOT / "source/matterix_tasks/matterix_tasks/test_dev_tasks/test_franka_vialplate.py"


def test_promoted_payloads_have_complete_static_contract():
"""Verify files, frame count, hashes, licenses, and official-only task paths."""
required_files = [
VIAL_ROOT / "fisherbrand_03-339-21f_z_up_fixed.usda",
VIAL_ROOT / "files/fisherbrand_03-339-21f_z_up_fixed_mesh.usda",
VIAL_ROOT / "files/Fisherbrand_Vial_Z_UP_FIXED.usdc",
VIAL_ROOT / "provenance/LICENSE.asset.txt",
VIAL_ROOT / "provenance/NOTICE.md",
VIAL_ROOT / "provenance/provenance.yaml",
HOLDER_ROOT / "3_5_vialplate_free_standing.usda",
HOLDER_ROOT / "3_5_vialplate_free_standing_frames.usda",
HOLDER_ROOT / "files/3_5_vialplate_free_standing_mesh.usda",
HOLDER_ROOT / "holder-hole-frame-contract.json",
HOLDER_ROOT / "provenance/LICENSE.asset.txt",
HOLDER_ROOT / "provenance/NOTICE.md",
HOLDER_ROOT / "provenance/asset_metadata.yaml",
HOLDER_ROOT / "provenance/license_record_draft.txt",
]
missing = [str(path) for path in required_files if not path.is_file()]
assert not missing, f"missing promoted payload files: {missing}"

contract_path = HOLDER_ROOT / "holder-hole-frame-contract.json"
contract = json.loads(contract_path.read_text(encoding="utf-8"))
frames = contract["frames"]
assert len(frames) == 15
assert len({frame["name"] for frame in frames}) == 15
selection = contract["initial_dynamic_vial_set"]
assert selection["pick_vial"] == "hole_middle_center"
assert len(selection["witness_vials"]) == 3

metadata = (HOLDER_ROOT / "provenance/asset_metadata.yaml").read_text(encoding="utf-8")
assert "original_license: Public Domain" in metadata
assert "package_license: CC0-1.0-Universal" in metadata
assert hashlib.sha256(contract_path.read_bytes()).hexdigest() in metadata

holder_license = (HOLDER_ROOT / "provenance/license_record_draft.txt").read_text(encoding="utf-8")
assert "https://3d.nih.gov/entries/3DPX-000429" in holder_license
assert "Public Domain" in holder_license
assert "CC BY 4.0" not in holder_license
assert "Pending" not in holder_license

vial_provenance = (VIAL_ROOT / "provenance/provenance.yaml").read_text(encoding="utf-8")
assert "asset_status: promoted_to_official_matterix_data" in vial_provenance
assert "canonical_visual_source_relative_path: not_packaged_in_official_data" in vial_provenance
assert "creation_method: user_authored_self_modelled_from_official_specification_and_dimensions" in vial_provenance
assert "license_status: CC0-1.0-Universal" in vial_provenance
assert "manufacturer_cad_or_texture_copied: false" in vial_provenance

vial_notice = (VIAL_ROOT / "provenance/NOTICE.md").read_text(encoding="utf-8")
assert "This promoted Matterix asset" in vial_notice
assert "The candidate is" not in vial_notice

task = TASK_PATH.read_text(encoding="utf-8")
assert "MATTERIX_PHASE_B_ASSETS_ROOT" not in task
assert "MATTERIX_VIAL_USD" not in task
assert "asset_workbench/phase_b_candidates" not in task
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import gymnasium as gym
import os

from . import test_franka_beaker_lift, test_franka_beakers, test_particle_systems, test_semantics_heat_transfer
from . import test_franka_beaker_lift, test_franka_beakers, test_franka_vialplate, test_particle_systems, test_semantics_heat_transfer

##
# Register Gym environments.
Expand Down Expand Up @@ -39,6 +39,15 @@
disable_env_checker=True,
)

gym.register(
id="Matterix-Test-Vialplate-Franka-v1",
entry_point="matterix.envs:MatterixBaseEnv",
kwargs={
"env_cfg_entry_point": test_franka_vialplate.FrankaVialplateEnvTestCfg,
},
disable_env_checker=True,
)

gym.register(
id="Matterix-Test-Semantics-Heat-Transfer-Franka-v1",
entry_point="matterix.envs:MatterixBaseEnv",
Expand Down
Loading
Loading