Добавлены 3д объекты в виртуальный мир (молотки 5шт)
This commit is contained in:
@@ -17,6 +17,7 @@ from iiwa_genesis.config import ROOT
|
||||
def test_cpu_rgbd_scene(tmp_path, mode, names):
|
||||
cfg = yaml.safe_load((ROOT / "configs/scene.yaml").read_text())
|
||||
cfg["scene"]["mode"] = mode
|
||||
cfg["tools"] = {"allowed": ["hammer1"], "seed": 17}
|
||||
cfg["output"]["directory"] = str(tmp_path / "frames")
|
||||
path = tmp_path / "scene.yaml"
|
||||
path.write_text(yaml.safe_dump(cfg))
|
||||
@@ -26,6 +27,7 @@ def test_cpu_rgbd_scene(tmp_path, mode, names):
|
||||
"--headless", "--backend", "cpu", "--steps", "4", "--save-frames"],
|
||||
env=env, capture_output=True, text=True, timeout=600)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
assert result.stdout.count("Table tool: hammer1;") == 1
|
||||
captures = list((tmp_path / "frames").glob("*/*.npz"))
|
||||
assert len(captures) == len(names)
|
||||
for name in names:
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""Opt-in contact regression using real exported meshes and the real table."""
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import pytest
|
||||
|
||||
from iiwa_genesis.config import ROOT
|
||||
|
||||
|
||||
@pytest.mark.skipif(os.environ.get("IIWA_GENESIS_SMOKE") != "1",
|
||||
reason="set IIWA_GENESIS_SMOKE=1 for real tool/table contact tests")
|
||||
def test_all_hammers_fall_rest_and_move_on_table():
|
||||
script = r'''
|
||||
import sys
|
||||
import numpy as np
|
||||
sys.path.insert(0, "src")
|
||||
from iiwa_genesis.config import load_config
|
||||
from iiwa_genesis.runtime import import_genesis
|
||||
from iiwa_genesis.tools import add_table_tool
|
||||
|
||||
gs = import_genesis()
|
||||
gs.init(backend=gs.cpu, seed=42, logging_level="warning")
|
||||
try:
|
||||
for number in range(1, 6):
|
||||
cfg = load_config("configs/scene.yaml")
|
||||
cfg["tools"] = {"allowed": [f"hammer{number}"], "seed": 10}
|
||||
scene = gs.Scene(show_viewer=False, show_FPS=False,
|
||||
sim_options=gs.options.SimOptions(dt=1/120, gravity=(0, 0, -9.81)))
|
||||
scene.add_entity(gs.morphs.URDF(file=str(cfg["table"]["urdf"]), fixed=True,
|
||||
merge_fixed_links=False, decimate=False))
|
||||
tool = add_table_tool(gs, scene, cfg)
|
||||
scene.build()
|
||||
assert len(scene.entities) == 2 # one table and exactly one tool
|
||||
assert tool.n_dofs == 6 and tool.n_links == 1 and tool.n_geoms > 1
|
||||
assert float(tool.get_mass()) > 0
|
||||
initial = tool.get_AABB().cpu().numpy()
|
||||
np.testing.assert_allclose(initial[0, 2], .805, atol=.002)
|
||||
assert initial[1, 2] < .87
|
||||
for _ in range(120):
|
||||
scene.step()
|
||||
rest = tool.get_AABB().cpu().numpy()
|
||||
assert np.isfinite(rest).all()
|
||||
assert .797 < rest[0, 2] < .803, (number, initial, rest)
|
||||
assert rest[0, 2] < initial[0, 2] - .001
|
||||
assert np.linalg.norm(tool.get_vel().cpu().numpy()) < .03
|
||||
# An imparted horizontal velocity moves the free rigid assembly; it
|
||||
# stays supported by the tabletop and friction slows it down.
|
||||
before = tool.get_pos().cpu().numpy().copy()
|
||||
tool.set_dofs_velocity([.3, 0, 0, 0, 0, 0])
|
||||
for _ in range(60):
|
||||
scene.step()
|
||||
after = tool.get_pos().cpu().numpy()
|
||||
assert np.linalg.norm(after[:2] - before[:2]) > .001, (number, before, after)
|
||||
assert .797 < float(tool.get_AABB()[0, 2]) < .803
|
||||
print(f"hammer{number}: contact, rest and dynamic movement OK", flush=True)
|
||||
scene.destroy()
|
||||
finally:
|
||||
gs.destroy()
|
||||
'''
|
||||
env = os.environ.copy()
|
||||
env.pop("PYTHONPATH", None)
|
||||
env.setdefault("PYOPENGL_PLATFORM", "egl")
|
||||
result = subprocess.run([sys.executable, "-c", script], cwd=ROOT,
|
||||
env=env, capture_output=True, text=True, timeout=900)
|
||||
assert result.returncode == 0, result.stdout + result.stderr
|
||||
@@ -0,0 +1,107 @@
|
||||
import numpy as np
|
||||
import pytest
|
||||
from scipy.spatial.transform import Rotation
|
||||
import yaml
|
||||
|
||||
from iiwa_genesis.config import ROOT, load_config
|
||||
|
||||
|
||||
def tool_config(tmp_path, tools):
|
||||
data = yaml.safe_load((ROOT / "configs/scene.yaml").read_text())
|
||||
data["tools"] = tools
|
||||
path = tmp_path / "scene.yaml"
|
||||
path.write_text(yaml.safe_dump(data))
|
||||
return load_config(path)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("allowed", [[], "hammer1", [1], ["hammer1", "hammer1"],
|
||||
["missing"], ["../hammer1"], ["hammer1.obj"]])
|
||||
def test_invalid_tool_allowlist_is_rejected(tmp_path, allowed):
|
||||
with pytest.raises(ValueError, match="tools.allowed"):
|
||||
tool_config(tmp_path, {"allowed": allowed})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("seed", [True, 1.5, "42", -1])
|
||||
def test_invalid_tool_seed_is_rejected(tmp_path, seed):
|
||||
with pytest.raises(ValueError, match="tools.seed"):
|
||||
tool_config(tmp_path, {"allowed": ["hammer1"], "seed": seed})
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tools", [None, ["hammer1"]])
|
||||
def test_invalid_tools_section_is_rejected(tmp_path, tools):
|
||||
with pytest.raises(ValueError, match="tools"):
|
||||
tool_config(tmp_path, tools)
|
||||
|
||||
|
||||
def test_empty_config_reports_validation_error(tmp_path):
|
||||
path = tmp_path / "empty.yaml"
|
||||
path.write_text("")
|
||||
with pytest.raises(ValueError, match="configuration"):
|
||||
load_config(path)
|
||||
|
||||
|
||||
def test_tool_defaults_and_singleton_selection(tmp_path):
|
||||
from iiwa_genesis.tools import choose_tool_spawn
|
||||
|
||||
cfg = tool_config(tmp_path, {"allowed": ["hammer3"]})
|
||||
assert cfg["tools"]["seed"] is None
|
||||
assert choose_tool_spawn(cfg).name == "hammer3"
|
||||
|
||||
|
||||
def test_seed_repeats_spawn_and_only_allowed_tools_can_be_selected(tmp_path):
|
||||
from iiwa_genesis.tools import choose_tool_spawn
|
||||
|
||||
cfg = tool_config(tmp_path, {"allowed": ["hammer2", "hammer5"], "seed": 123})
|
||||
first = choose_tool_spawn(cfg)
|
||||
assert choose_tool_spawn(cfg) == first
|
||||
cfg["scene"]["seed"] = 999
|
||||
assert choose_tool_spawn(cfg) == first
|
||||
choices = set()
|
||||
positions = set()
|
||||
for seed in range(12):
|
||||
cfg["tools"]["seed"] = seed
|
||||
spawn = choose_tool_spawn(cfg)
|
||||
choices.add(spawn.name)
|
||||
positions.add(spawn.position)
|
||||
assert choices == {"hammer2", "hammer5"}
|
||||
assert len(positions) > 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", [f"hammer{i}" for i in range(1, 6)])
|
||||
@pytest.mark.parametrize("table_pose", [((0, 0, 0), (0, 0, 0)),
|
||||
((1.2, -.7, .2), (5, -3, 60))])
|
||||
def test_exported_tool_fits_table_without_blender_offset(tmp_path, name, table_pose):
|
||||
from iiwa_genesis.tools import choose_tool_spawn
|
||||
|
||||
cfg = tool_config(tmp_path, {"allowed": [name], "seed": 9})
|
||||
cfg["table"]["position"], cfg["table"]["euler"] = table_pose
|
||||
# Read the authored Y-up vertices independently of placement code.
|
||||
raw = np.array([[float(x) for x in line.split()[1:4]]
|
||||
for line in (ROOT / f"iiwa_genesis_description/tools/hammer/{name}.obj").read_text().splitlines()
|
||||
if line.startswith("v ")])
|
||||
zup = raw[:, [0, 2, 1]] * [1, -1, 1]
|
||||
table_rotation = Rotation.from_euler("xyz", table_pose[1], degrees=True)
|
||||
for seed in (0, 1, 9, 37):
|
||||
cfg["tools"]["seed"] = seed
|
||||
spawn = choose_tool_spawn(cfg)
|
||||
world = Rotation.from_quat(spawn.quaternion, scalar_first=True).apply(zup) + spawn.position
|
||||
local = table_rotation.inv().apply(world - table_pose[0])
|
||||
assert local[:, 0].min() >= .06 - 1e-9
|
||||
assert local[:, 0].max() <= .58 + 1e-9
|
||||
assert local[:, 1].min() >= -.48 - 1e-9
|
||||
assert local[:, 1].max() <= .48 + 1e-9
|
||||
assert local[:, 2].min() == pytest.approx(.805)
|
||||
assert local[:, 2].max() < .87 # lying flat, not standing on the handle
|
||||
|
||||
|
||||
def test_missing_material_is_rejected(tmp_path, monkeypatch):
|
||||
from iiwa_genesis import config
|
||||
|
||||
description = tmp_path / "description"
|
||||
asset = description / "tools/hammer/hammer1.obj"
|
||||
asset.parent.mkdir(parents=True)
|
||||
asset.write_text("mtllib hammer1.mtl\nv 0 0 0\n")
|
||||
(description / "urdf").symlink_to(config.DESCRIPTION / "urdf", target_is_directory=True)
|
||||
monkeypatch.setattr(config, "DESCRIPTION", description)
|
||||
with pytest.raises(ValueError, match="hammer1.mtl"):
|
||||
tool_config(tmp_path, {"allowed": ["hammer1"]})
|
||||
Reference in New Issue
Block a user