A-SW Hub / Repositories
Local Gitea · READ ONLY
← 저장소 Repositories
REPOSITORY

metafarmers/visual-servo-manipulation Private

[참조 MVP] 비주얼 서보 매니퓰레이션 — 선택한 작업대상의 위치 오차를 로봇팔 정렬 명령으로 변환하는 참조 MVP입니다.

https://git.agrithing.ai/metafarmers/visual-servo-manipulation.git
4 commits
COMMIT DETAIL

feat: 비주얼 서보 매니퓰레이션 구체 MVP 구현

기관 승인 및 자동 품질검사를 통과한 구체 MVP 0.2.0 병합 Co-authored-by: meta-dev <[email protected]>

meta-dev <[email protected]> · 2026. 8. 3. 오후 3:12:36
1df47d4f61aad95a4dd75626009fdcf03bba2e53
8 changed files +71 −8 parent ab79649
modified README.md +7−1 View file
@@ -11,6 +11,12 @@
11 11
12 12 선택한 작업대상의 위치 오차를 로봇팔 정렬 명령으로 변환하는 참조 MVP입니다.
13 13
14 + ## MVP 동작 범위
15 +
16 + - 축별 이동량 제한
17 + - 정렬 잔차 계산
18 + - 허용오차 기반 정렬 완료 판정
19 +
14 20 ## 실행
15 21
16 22 ```bash
@@ -18,4 +24,4 @@ python run.py examples/input.json
18 24 python -m unittest discover -s tests -v
19 25 ```
20 26
21 실제 기관 구현은 `contracts/component.interface.json`의 저장소 식별자와 제품 기준선 연결 규칙을 유지하면서 교체할 수 있습니다.
27 + 정상 입력은 `examples/input.json`, 차단·안전 경로는 `examples/failure-input.json`으로 재현합니다. 실제 기관 구현은 `contracts/component.interface.json`의 저장소 식별자와 제품 기준선 연결 규칙을 유지하면서 교체할 수 있습니다.
modified asw_component/component.py +20−3 View file
@@ -1,7 +1,24 @@
1 1 from __future__ import annotations
2 + from math import sqrt
2 3
3 4
4 5 def execute(payload: dict) -> dict:
5 if not isinstance(payload, dict) or not payload:
6 raise ValueError("a non-empty object is required")
7 return {"component": "metafarmers/visual-servo-manipulation", "accepted": True, "status": "READY", "target": "acu", "input_fields": sorted(payload.keys())}
6 + error = payload.get("positionErrorMm")
7 + if not isinstance(error, list) or len(error) != 3:
8 + raise ValueError("positionErrorMm must contain x, y, z")
9 + max_step = float(payload.get("maxStepMm", 10))
10 + tolerance = float(payload.get("toleranceMm", 2))
11 + if max_step <= 0 or tolerance < 0:
12 + raise ValueError("motion limits must be positive")
13 + vector = [float(value) for value in error]
14 + step = [max(-max_step, min(max_step, value)) for value in vector]
15 + residual_vector = [value - motion for value, motion in zip(vector, step)]
16 + residual = sqrt(sum(value * value for value in residual_vector))
17 + converged = sqrt(sum(value * value for value in vector)) <= tolerance
18 + if converged:
19 + step = [0.0, 0.0, 0.0]
20 + residual = 0.0
21 + return {"component": "metafarmers/visual-servo-manipulation", "accepted": True,
22 + "status": "ALIGNED" if converged else "ALIGNING", "target": "acu",
23 + "targetId": str(payload.get("targetId", "unknown")), "stepMm": step,
24 + "residualMm": round(residual, 3), "converged": converged}
modified contracts/component.interface.json +18−1 View file
@@ -5,7 +5,12 @@
5 5 "required": [
6 6 "component",
7 7 "accepted",
8 "status"
8 + "status",
9 + "target",
10 + "targetId",
11 + "stepMm",
12 + "residualMm",
13 + "converged"
9 14 ],
10 15 "properties": {
11 16 "component": {
@@ -19,6 +24,18 @@
19 24 },
20 25 "target": {
21 26 "const": "acu"
27 + },
28 + "targetId": {
29 + "type": "string"
30 + },
31 + "stepMm": {
32 + "type": "array"
33 + },
34 + "residualMm": {
35 + "type": "number"
36 + },
37 + "converged": {
38 + "type": "boolean"
22 39 }
23 40 }
24 41 }
added examples/failure-input.json +10−0 View file
@@ -0,0 +1,10 @@
1 + {
2 + "targetId": "fruit-17",
3 + "positionErrorMm": [
4 + 0.4,
5 + -0.3,
6 + 0.2
7 + ],
8 + "maxStepMm": 6,
9 + "toleranceMm": 2
10 + }
modified examples/input.json +2−1 View file
@@ -5,5 +5,6 @@
5 5 -5,
6 6 8
7 7 ],
8 "maxStepMm": 20
8 + "maxStepMm": 6,
9 + "toleranceMm": 2
9 10 }
modified pyproject.toml +1−1 View file
@@ -1,5 +1,5 @@
1 1 [project]
2 2 name = "visual-servo-manipulation"
3 version = "0.1.0"
3 + version = "0.2.0"
4 4 description = "선택한 작업대상의 위치 오차를 로봇팔 정렬 명령으로 변환하는 참조 MVP입니다."
5 5 requires-python = ">=3.11"
modified release/manifest.json +1−1 View file
@@ -4,7 +4,7 @@
4 4 "displayName": "비주얼 서보 매니퓰레이션",
5 5 "owner": "주식회사 메타파머스",
6 6 "implementationAuthor": "주식회사 비아 (연동 검증용 참조 구현)",
7 "version": "0.1.0-mvp",
7 + "version": "0.2.0-mvp",
8 8 "domain": "Manipulation",
9 9 "status": "REFERENCE_MVP",
10 10 "compatibility": {
modified tests/test_component.py +12−0 View file
@@ -12,6 +12,18 @@ class ComponentTest(unittest.TestCase):
12 12 result = execute(payload)
13 13 self.assertTrue(result["accepted"])
14 14 self.assertEqual(result["component"], "metafarmers/visual-servo-manipulation")
15 + self.assertEqual(result["target"], "acu")
16 + self.assertEqual(result["status"], "ALIGNING")
17 + self.assertEqual(result["stepMm"], [6.0, -5.0, 6.0])
18 + self.assertAlmostEqual(result["residualMm"], 6.325, places=3)
19 +
20 + def test_safety_or_failure_path(self):
21 + payload = json.loads((ROOT / "examples/failure-input.json").read_text(encoding="utf-8"))
22 + result = execute(payload)
23 + self.assertEqual(result["component"], "metafarmers/visual-servo-manipulation")
24 + self.assertTrue(result["accepted"])
25 + self.assertTrue(result["converged"])
26 + self.assertEqual(result["stepMm"], [0.0, 0.0, 0.0])
15 27
16 28 if __name__ == "__main__":
17 29 unittest.main()