foodforthought-cli 0.2.8__py3-none-any.whl → 0.3.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ate/__init__.py +6 -0
- ate/__main__.py +16 -0
- ate/auth/__init__.py +1 -0
- ate/auth/device_flow.py +141 -0
- ate/auth/token_store.py +96 -0
- ate/behaviors/__init__.py +12 -0
- ate/behaviors/approach.py +399 -0
- ate/cli.py +855 -4551
- ate/client.py +90 -0
- ate/commands/__init__.py +168 -0
- ate/commands/auth.py +389 -0
- ate/commands/bridge.py +448 -0
- ate/commands/data.py +185 -0
- ate/commands/deps.py +111 -0
- ate/commands/generate.py +384 -0
- ate/commands/memory.py +907 -0
- ate/commands/parts.py +166 -0
- ate/commands/primitive.py +399 -0
- ate/commands/protocol.py +288 -0
- ate/commands/recording.py +524 -0
- ate/commands/repo.py +154 -0
- ate/commands/simulation.py +291 -0
- ate/commands/skill.py +303 -0
- ate/commands/skills.py +487 -0
- ate/commands/team.py +147 -0
- ate/commands/workflow.py +271 -0
- ate/detection/__init__.py +38 -0
- ate/detection/base.py +142 -0
- ate/detection/color_detector.py +402 -0
- ate/detection/trash_detector.py +322 -0
- ate/drivers/__init__.py +18 -6
- ate/drivers/ble_transport.py +405 -0
- ate/drivers/mechdog.py +360 -24
- ate/drivers/wifi_camera.py +477 -0
- ate/interfaces/__init__.py +16 -0
- ate/interfaces/base.py +2 -0
- ate/interfaces/sensors.py +247 -0
- ate/llm_proxy.py +239 -0
- ate/memory/__init__.py +35 -0
- ate/memory/cloud.py +244 -0
- ate/memory/context.py +269 -0
- ate/memory/embeddings.py +184 -0
- ate/memory/export.py +26 -0
- ate/memory/merge.py +146 -0
- ate/memory/migrate/__init__.py +34 -0
- ate/memory/migrate/base.py +89 -0
- ate/memory/migrate/pipeline.py +189 -0
- ate/memory/migrate/sources/__init__.py +13 -0
- ate/memory/migrate/sources/chroma.py +170 -0
- ate/memory/migrate/sources/pinecone.py +120 -0
- ate/memory/migrate/sources/qdrant.py +110 -0
- ate/memory/migrate/sources/weaviate.py +160 -0
- ate/memory/reranker.py +353 -0
- ate/memory/search.py +26 -0
- ate/memory/store.py +548 -0
- ate/recording/__init__.py +42 -3
- ate/recording/session.py +12 -2
- ate/recording/visual.py +416 -0
- ate/robot/__init__.py +142 -0
- ate/robot/agentic_servo.py +856 -0
- ate/robot/behaviors.py +493 -0
- ate/robot/ble_capture.py +1000 -0
- ate/robot/ble_enumerate.py +506 -0
- ate/robot/calibration.py +88 -3
- ate/robot/calibration_state.py +388 -0
- ate/robot/commands.py +143 -11
- ate/robot/direction_calibration.py +554 -0
- ate/robot/discovery.py +104 -2
- ate/robot/llm_system_id.py +654 -0
- ate/robot/locomotion_calibration.py +508 -0
- ate/robot/marker_generator.py +611 -0
- ate/robot/perception.py +502 -0
- ate/robot/primitives.py +614 -0
- ate/robot/profiles.py +6 -0
- ate/robot/registry.py +5 -2
- ate/robot/servo_mapper.py +1153 -0
- ate/robot/skill_upload.py +285 -3
- ate/robot/target_calibration.py +500 -0
- ate/robot/teach.py +515 -0
- ate/robot/types.py +242 -0
- ate/robot/visual_labeler.py +9 -0
- ate/robot/visual_servo_loop.py +494 -0
- ate/robot/visual_servoing.py +570 -0
- ate/robot/visual_system_id.py +906 -0
- ate/transports/__init__.py +121 -0
- ate/transports/base.py +394 -0
- ate/transports/ble.py +405 -0
- ate/transports/hybrid.py +444 -0
- ate/transports/serial.py +345 -0
- ate/urdf/__init__.py +30 -0
- ate/urdf/capture.py +582 -0
- ate/urdf/cloud.py +491 -0
- ate/urdf/collision.py +271 -0
- ate/urdf/commands.py +708 -0
- ate/urdf/depth.py +360 -0
- ate/urdf/inertial.py +312 -0
- ate/urdf/kinematics.py +330 -0
- ate/urdf/lifting.py +415 -0
- ate/urdf/meshing.py +300 -0
- ate/urdf/models/__init__.py +110 -0
- ate/urdf/models/depth_anything.py +253 -0
- ate/urdf/models/sam2.py +324 -0
- ate/urdf/motion_analysis.py +396 -0
- ate/urdf/pipeline.py +468 -0
- ate/urdf/scale.py +256 -0
- ate/urdf/scan_session.py +411 -0
- ate/urdf/segmentation.py +299 -0
- ate/urdf/synthesis.py +319 -0
- ate/urdf/topology.py +336 -0
- ate/urdf/validation.py +371 -0
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.1.dist-info}/METADATA +1 -1
- foodforthought_cli-0.3.1.dist-info/RECORD +166 -0
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.1.dist-info}/WHEEL +1 -1
- foodforthought_cli-0.2.8.dist-info/RECORD +0 -73
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.1.dist-info}/entry_points.txt +0 -0
- {foodforthought_cli-0.2.8.dist-info → foodforthought_cli-0.3.1.dist-info}/top_level.txt +0 -0
ate/urdf/collision.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Collision mesh generation via convex decomposition.
|
|
3
|
+
|
|
4
|
+
This module handles Phase 4b of the pipeline:
|
|
5
|
+
1. Load visual meshes
|
|
6
|
+
2. Decompose into convex hulls using CoACD or V-HACD
|
|
7
|
+
3. Save collision meshes
|
|
8
|
+
|
|
9
|
+
Physics engines require convex collision shapes for stable simulation.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
from typing import Dict, List, Optional, Tuple
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
import numpy as np
|
|
20
|
+
NUMPY_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
NUMPY_AVAILABLE = False
|
|
23
|
+
np = None
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
import trimesh
|
|
27
|
+
TRIMESH_AVAILABLE = True
|
|
28
|
+
except ImportError:
|
|
29
|
+
TRIMESH_AVAILABLE = False
|
|
30
|
+
trimesh = None
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CollisionError(Exception):
|
|
34
|
+
"""Error during collision mesh generation."""
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def coacd_decompose(
|
|
39
|
+
mesh: "trimesh.Trimesh",
|
|
40
|
+
max_hulls: int = 8,
|
|
41
|
+
threshold: float = 0.05,
|
|
42
|
+
) -> List["trimesh.Trimesh"]:
|
|
43
|
+
"""
|
|
44
|
+
Decompose mesh into convex hulls using CoACD.
|
|
45
|
+
|
|
46
|
+
CoACD (Approximate Convex Decomposition) is more accurate than V-HACD
|
|
47
|
+
but may not be available on all systems.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
mesh: Input mesh to decompose
|
|
51
|
+
max_hulls: Maximum number of convex hulls
|
|
52
|
+
threshold: Concavity threshold
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
List of convex hull meshes
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
import coacd
|
|
59
|
+
|
|
60
|
+
# CoACD expects vertices and faces
|
|
61
|
+
vertices = np.array(mesh.vertices, dtype=np.float64)
|
|
62
|
+
faces = np.array(mesh.faces, dtype=np.int32)
|
|
63
|
+
|
|
64
|
+
# Run decomposition
|
|
65
|
+
parts = coacd.run_coacd(
|
|
66
|
+
vertices,
|
|
67
|
+
faces,
|
|
68
|
+
threshold=threshold,
|
|
69
|
+
max_convex_hull=max_hulls,
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
# Convert to trimesh objects
|
|
73
|
+
hulls = []
|
|
74
|
+
for verts, tris in parts:
|
|
75
|
+
hull = trimesh.Trimesh(vertices=verts, faces=tris)
|
|
76
|
+
hull.fix_normals()
|
|
77
|
+
hulls.append(hull)
|
|
78
|
+
|
|
79
|
+
logger.debug(f"CoACD decomposed into {len(hulls)} convex hulls")
|
|
80
|
+
return hulls
|
|
81
|
+
|
|
82
|
+
except ImportError:
|
|
83
|
+
logger.warning("CoACD not available, falling back to V-HACD")
|
|
84
|
+
return vhacd_decompose(mesh, max_hulls)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def vhacd_decompose(
|
|
88
|
+
mesh: "trimesh.Trimesh",
|
|
89
|
+
max_hulls: int = 8,
|
|
90
|
+
resolution: int = 100000,
|
|
91
|
+
) -> List["trimesh.Trimesh"]:
|
|
92
|
+
"""
|
|
93
|
+
Decompose mesh into convex hulls using V-HACD.
|
|
94
|
+
|
|
95
|
+
V-HACD (Volumetric Hierarchical Approximate Convex Decomposition)
|
|
96
|
+
is a classic algorithm available in trimesh.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
mesh: Input mesh to decompose
|
|
100
|
+
max_hulls: Maximum number of convex hulls
|
|
101
|
+
resolution: Voxelization resolution
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
List of convex hull meshes
|
|
105
|
+
"""
|
|
106
|
+
if not TRIMESH_AVAILABLE:
|
|
107
|
+
raise CollisionError("trimesh not available")
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
# Try trimesh's built-in convex decomposition
|
|
111
|
+
hulls = mesh.convex_decomposition(
|
|
112
|
+
maxhulls=max_hulls,
|
|
113
|
+
resolution=resolution,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
if isinstance(hulls, trimesh.Trimesh):
|
|
117
|
+
hulls = [hulls]
|
|
118
|
+
|
|
119
|
+
logger.debug(f"V-HACD decomposed into {len(hulls)} convex hulls")
|
|
120
|
+
return hulls
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
logger.warning(f"V-HACD failed: {e}, using single convex hull")
|
|
124
|
+
return [mesh.convex_hull]
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def simple_convex_hull(mesh: "trimesh.Trimesh") -> List["trimesh.Trimesh"]:
|
|
128
|
+
"""
|
|
129
|
+
Create a single convex hull from mesh.
|
|
130
|
+
|
|
131
|
+
Simplest collision shape, fastest simulation.
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
mesh: Input mesh
|
|
135
|
+
|
|
136
|
+
Returns:
|
|
137
|
+
List containing single convex hull
|
|
138
|
+
"""
|
|
139
|
+
if not TRIMESH_AVAILABLE:
|
|
140
|
+
raise CollisionError("trimesh not available")
|
|
141
|
+
|
|
142
|
+
hull = mesh.convex_hull
|
|
143
|
+
return [hull]
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def generate_collision_mesh(
|
|
147
|
+
visual_mesh_path: Path,
|
|
148
|
+
output_path: Path,
|
|
149
|
+
max_hulls: int = 8,
|
|
150
|
+
method: str = "auto",
|
|
151
|
+
) -> Path:
|
|
152
|
+
"""
|
|
153
|
+
Generate collision mesh from visual mesh.
|
|
154
|
+
|
|
155
|
+
Args:
|
|
156
|
+
visual_mesh_path: Path to visual mesh (OBJ)
|
|
157
|
+
output_path: Path for collision mesh output
|
|
158
|
+
max_hulls: Maximum convex hulls
|
|
159
|
+
method: "coacd", "vhacd", "hull", or "auto"
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Path to collision mesh
|
|
163
|
+
"""
|
|
164
|
+
if not TRIMESH_AVAILABLE:
|
|
165
|
+
raise CollisionError("trimesh not available. Run: pip install trimesh")
|
|
166
|
+
|
|
167
|
+
# Load visual mesh
|
|
168
|
+
mesh = trimesh.load(str(visual_mesh_path))
|
|
169
|
+
|
|
170
|
+
if not isinstance(mesh, trimesh.Trimesh):
|
|
171
|
+
# Handle scene objects
|
|
172
|
+
if hasattr(mesh, 'geometry'):
|
|
173
|
+
meshes = list(mesh.geometry.values())
|
|
174
|
+
if meshes:
|
|
175
|
+
mesh = meshes[0]
|
|
176
|
+
else:
|
|
177
|
+
raise CollisionError(f"No geometry in {visual_mesh_path}")
|
|
178
|
+
else:
|
|
179
|
+
raise CollisionError(f"Invalid mesh type in {visual_mesh_path}")
|
|
180
|
+
|
|
181
|
+
# Decompose into convex hulls
|
|
182
|
+
if method == "auto":
|
|
183
|
+
try:
|
|
184
|
+
hulls = coacd_decompose(mesh, max_hulls)
|
|
185
|
+
except Exception:
|
|
186
|
+
try:
|
|
187
|
+
hulls = vhacd_decompose(mesh, max_hulls)
|
|
188
|
+
except Exception:
|
|
189
|
+
hulls = simple_convex_hull(mesh)
|
|
190
|
+
elif method == "coacd":
|
|
191
|
+
hulls = coacd_decompose(mesh, max_hulls)
|
|
192
|
+
elif method == "vhacd":
|
|
193
|
+
hulls = vhacd_decompose(mesh, max_hulls)
|
|
194
|
+
elif method == "hull":
|
|
195
|
+
hulls = simple_convex_hull(mesh)
|
|
196
|
+
else:
|
|
197
|
+
raise CollisionError(f"Unknown decomposition method: {method}")
|
|
198
|
+
|
|
199
|
+
# Combine hulls into single mesh for export
|
|
200
|
+
if len(hulls) == 1:
|
|
201
|
+
combined = hulls[0]
|
|
202
|
+
else:
|
|
203
|
+
combined = trimesh.util.concatenate(hulls)
|
|
204
|
+
|
|
205
|
+
# Export
|
|
206
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
207
|
+
combined.export(str(output_path))
|
|
208
|
+
|
|
209
|
+
logger.info(
|
|
210
|
+
f"Generated collision mesh: {output_path} "
|
|
211
|
+
f"({len(hulls)} hulls, {len(combined.faces)} faces)"
|
|
212
|
+
)
|
|
213
|
+
return output_path
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def generate_all_collision_meshes(
|
|
217
|
+
session: "ScanSession",
|
|
218
|
+
max_hulls: int = 8,
|
|
219
|
+
method: str = "auto",
|
|
220
|
+
progress_callback: Optional[callable] = None,
|
|
221
|
+
) -> Dict[str, Path]:
|
|
222
|
+
"""
|
|
223
|
+
Generate collision meshes for all links.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
session: ScanSession with visual meshes
|
|
227
|
+
max_hulls: Maximum hulls per link
|
|
228
|
+
method: Decomposition method
|
|
229
|
+
progress_callback: Optional progress callback
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
Dict mapping link_name -> collision_mesh_path
|
|
233
|
+
"""
|
|
234
|
+
meshes_dir = session.meshes_dir
|
|
235
|
+
visual_meshes = list(meshes_dir.glob("*_visual.obj"))
|
|
236
|
+
|
|
237
|
+
if not visual_meshes:
|
|
238
|
+
raise CollisionError(f"No visual meshes found in {meshes_dir}")
|
|
239
|
+
|
|
240
|
+
result = {}
|
|
241
|
+
total = len(visual_meshes)
|
|
242
|
+
|
|
243
|
+
for i, visual_path in enumerate(visual_meshes):
|
|
244
|
+
link_name = visual_path.stem.replace("_visual", "")
|
|
245
|
+
collision_path = meshes_dir / f"{link_name}_collision.obj"
|
|
246
|
+
|
|
247
|
+
try:
|
|
248
|
+
generate_collision_mesh(visual_path, collision_path, max_hulls, method)
|
|
249
|
+
result[link_name] = collision_path
|
|
250
|
+
except Exception as e:
|
|
251
|
+
logger.error(f"Failed to generate collision mesh for {link_name}: {e}")
|
|
252
|
+
|
|
253
|
+
if progress_callback:
|
|
254
|
+
progress_callback(i + 1, total)
|
|
255
|
+
|
|
256
|
+
# Update session
|
|
257
|
+
session.metadata.mesh_complete = True
|
|
258
|
+
session.save_metadata()
|
|
259
|
+
|
|
260
|
+
logger.info(f"Generated {len(result)} collision meshes")
|
|
261
|
+
return result
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
__all__ = [
|
|
265
|
+
"CollisionError",
|
|
266
|
+
"coacd_decompose",
|
|
267
|
+
"vhacd_decompose",
|
|
268
|
+
"simple_convex_hull",
|
|
269
|
+
"generate_collision_mesh",
|
|
270
|
+
"generate_all_collision_meshes",
|
|
271
|
+
]
|