Add GH documentation tools, GIS import, terrain analysis
New Grasshopper tools: - grasshopper_delete_component: Remove components from canvas - grasshopper_add_group: Add comment groups around components - grasshopper_add_scribble: Add text annotations - grasshopper_rename_component: Rename component nicknames New GIS/Terrain tools: - rhino_import_geotiff: Import GeoTIFF as heightfield mesh - rhino_import_shapefile: Import ESRI Shapefiles - rhino_import_xyz: Import XYZ point clouds - rhino_transform_coordinates: Offset/transform coordinates - rhino_get_bounding_box: Get object bounds - rhino_create_grid: Create survey grids - rhino_create_site_section: Generate terrain profiles - rhino_calculate_area_volume: Calculate cut/fill volumes Added: - CLAUDE.md: Project documentation with dev guidelines - install.bat: Windows installer script - MeshTerrainHandler.cs: Separate handler for mesh/terrain/GIS 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
139
CLAUDE.md
Normal file
139
CLAUDE.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# RhinoMCP - Claude Code Projektdokumentation
|
||||
|
||||
## Projektübersicht
|
||||
|
||||
RhinoMCP ist ein MCP (Model Context Protocol) Plugin, das Claude AI direkten Zugriff auf Rhino 3D und Grasshopper ermöglicht. Es wurde für Landschaftsarchitektur, Straßeninfrastruktur und Regenwassermanagement entwickelt.
|
||||
|
||||
## Architektur
|
||||
|
||||
```text
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ Claude.ai / Claude Code │
|
||||
└──────────────────┬──────────────────────────────────────┘
|
||||
│ MCP Protocol (stdio)
|
||||
┌──────────────────▼──────────────────────────────────────┐
|
||||
│ MCP-Server (Python) │
|
||||
│ src/mcp-server/server.py │
|
||||
│ - Tool-Definitionen │
|
||||
│ - Request/Response Handling │
|
||||
└──────────────────┬──────────────────────────────────────┘
|
||||
│ HTTP (localhost:9000)
|
||||
┌──────────────────▼──────────────────────────────────────┐
|
||||
│ Rhino-Plugin (C# .rhp) │
|
||||
│ src/rhino-plugin/RhinoMCP/ │
|
||||
│ ├─ RhinoMcpPlugin.cs (Plugin-Einstieg) │
|
||||
│ ├─ HttpServer.cs (HTTP-Endpoint) │
|
||||
│ ├─ CommandHandler.cs (Rhino-Befehle) │
|
||||
│ ├─ GrasshopperHandler.cs(GH-Befehle) │
|
||||
│ └─ MeshTerrainHandler.cs(Mesh/GIS-Tools) │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Technische Details
|
||||
|
||||
- **Rhino-Version**: Rhino 9 WIP
|
||||
- **Framework**: .NET 7.0
|
||||
- **RhinoCommon/Grasshopper SDK**: 8.0.23304.9001
|
||||
- **HTTP-Server**: EmbedIO auf Port 9000
|
||||
- **MCP-Transport**: stdio
|
||||
|
||||
## Verfügbare Tools
|
||||
|
||||
### Rhino-Geometrie
|
||||
|
||||
- `rhino_create_point`, `rhino_create_line`, `rhino_create_polyline`
|
||||
- `rhino_create_circle`, `rhino_create_sphere`, `rhino_create_box`
|
||||
- `rhino_create_surface`, `rhino_create_curve`
|
||||
- `rhino_move`, `rhino_rotate`, `rhino_scale`, `rhino_copy`
|
||||
- `rhino_boolean_union`, `rhino_boolean_difference`, `rhino_boolean_intersection`
|
||||
- `rhino_export`, `rhino_run_command`
|
||||
|
||||
### Grasshopper
|
||||
|
||||
- `grasshopper_open_definition`, `grasshopper_get_definition_info`
|
||||
- `grasshopper_add_component`, `grasshopper_connect_components`
|
||||
- `grasshopper_add_slider`, `grasshopper_set_slider_value`
|
||||
- `grasshopper_set_toggle_value`, `grasshopper_set_panel_text`
|
||||
- `grasshopper_bake`, `grasshopper_save_definition`, `grasshopper_recompute`
|
||||
- `grasshopper_delete_component`, `grasshopper_add_group`
|
||||
- `grasshopper_add_scribble`, `grasshopper_rename_component`
|
||||
|
||||
### Mesh/Terrain/GIS
|
||||
|
||||
- `rhino_mesh_from_points`, `rhino_import_mesh`, `rhino_mesh_boolean`
|
||||
- `rhino_terrain_contours`, `rhino_terrain_slope_analysis`
|
||||
- `rhino_terrain_watershed`, `rhino_terrain_low_points`
|
||||
- `rhino_create_drainage_line`, `rhino_create_road_surface`
|
||||
- `rhino_import_geotiff`, `rhino_import_shapefile`, `rhino_import_xyz`
|
||||
- `rhino_transform_coordinates`, `rhino_get_bounding_box`
|
||||
- `rhino_create_grid`, `rhino_create_site_section`, `rhino_calculate_area_volume`
|
||||
|
||||
## Installation
|
||||
|
||||
1. Plugin bauen: `dotnet build --configuration Release`
|
||||
2. `install.bat` ausführen oder manuell nach `%APPDATA%\McNeel\Rhinoceros\9.0\Plug-ins\RhinoMCP (1.0.0)\` kopieren
|
||||
3. MCP-Server in Claude Code konfigurieren
|
||||
|
||||
## Entwicklungsrichtlinien
|
||||
|
||||
### Automatisches Commit bei Kapazitätswarnung
|
||||
|
||||
**WICHTIG**: Ab 85% Chat-Kapazität muss automatisch:
|
||||
|
||||
1. Aktueller Stand dokumentiert werden
|
||||
2. Alle Änderungen committet werden
|
||||
3. Zusammenfassung der offenen Aufgaben erstellt werden
|
||||
|
||||
### Code-Struktur
|
||||
|
||||
- **Maximale Dateigröße: 300 Zeilen pro Datei**
|
||||
- Bei Überschreitung: Logisch aufteilen in separate Handler-Klassen
|
||||
|
||||
### Aktuelle Aufteilungsempfehlungen
|
||||
|
||||
| Datei | Zeilen | Status | Empfehlung |
|
||||
| ---------------------- | ------ | -------- | --------------------------------------------------------- |
|
||||
| CommandHandler.cs | ~683 | Zu groß | Aufteilen in GeometryHandler, TransformHandler |
|
||||
| MeshTerrainHandler.cs | ~1509 | Zu groß | Aufteilen in MeshHandler, TerrainHandler, GISHandler, RoadHandler |
|
||||
| GrasshopperHandler.cs | ~671 | Zu groß | Aufteilen in GH_ComponentHandler, GH_DocumentHandler |
|
||||
| server.py | ~1185 | Zu groß | Tool-Definitionen in separate Module |
|
||||
|
||||
## Changelog
|
||||
|
||||
### 2025-12-29
|
||||
|
||||
- Initiale Entwicklung des RhinoMCP Plugins
|
||||
- Implementiert: Rhino-Geometrie, Grasshopper-Integration, Mesh/Terrain-Tools
|
||||
- Neue GH-Tools: `delete_component`, `add_group`, `add_scribble`, `rename_component`
|
||||
- GIS-Tools: GeoTIFF, Shapefile, XYZ Import
|
||||
- Terrain-Analyse: Konturlinien, Gefälle, Wasserscheiden
|
||||
|
||||
## Offene Aufgaben
|
||||
|
||||
- [ ] Code-Refactoring: Große Dateien aufteilen (max. 300 Zeilen)
|
||||
- [ ] Unit-Tests implementieren
|
||||
- [ ] Fehlerbehandlung verbessern
|
||||
- [ ] Dokumentation erweitern
|
||||
|
||||
## Projektstruktur
|
||||
|
||||
```text
|
||||
Rhino-MCP/
|
||||
├── CLAUDE.md # Diese Dokumentation
|
||||
├── README.md # Öffentliche Dokumentation
|
||||
├── LICENSE # MIT Lizenz
|
||||
├── install.bat # Windows-Installer
|
||||
├── src/
|
||||
│ ├── mcp-server/
|
||||
│ │ ├── server.py # MCP-Server (TODO: aufteilen)
|
||||
│ │ └── requirements.txt
|
||||
│ └── rhino-plugin/
|
||||
│ └── RhinoMCP/
|
||||
│ ├── RhinoMCP.csproj
|
||||
│ ├── RhinoMcpPlugin.cs
|
||||
│ ├── HttpServer.cs
|
||||
│ ├── CommandHandler.cs # TODO: aufteilen
|
||||
│ ├── GrasshopperHandler.cs # TODO: aufteilen
|
||||
│ └── MeshTerrainHandler.cs # TODO: aufteilen
|
||||
└── docs/ # Zusätzliche Dokumentation
|
||||
```
|
||||
20
install.bat
Normal file
20
install.bat
Normal file
@@ -0,0 +1,20 @@
|
||||
@echo off
|
||||
echo Installing RhinoMCP Plugin...
|
||||
|
||||
set RHINO_PLUGINS=%APPDATA%\McNeel\Rhinoceros\9.0\Plug-ins\RhinoMCP (1.0.0)
|
||||
set SOURCE_DIR=%~dp0src\rhino-plugin\RhinoMCP\bin\Release\net7.0-windows
|
||||
|
||||
if not exist "%RHINO_PLUGINS%" (
|
||||
mkdir "%RHINO_PLUGINS%"
|
||||
)
|
||||
|
||||
echo Copying files to: %RHINO_PLUGINS%
|
||||
xcopy /Y /E "%SOURCE_DIR%\*" "%RHINO_PLUGINS%\"
|
||||
|
||||
echo.
|
||||
echo Installation complete!
|
||||
echo.
|
||||
echo The plugin will load automatically when Rhino 9 starts.
|
||||
echo If Rhino is running, restart it or run: _PlugInManager to load RhinoMCP.dll
|
||||
echo.
|
||||
pause
|
||||
@@ -495,6 +495,425 @@ async def list_tools() -> list[Tool]:
|
||||
}
|
||||
),
|
||||
|
||||
# === Mesh Tools ===
|
||||
Tool(
|
||||
name="rhino_import_mesh",
|
||||
description="Import a mesh file (OBJ, STL, PLY, 3DS, FBX) into Rhino",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {"type": "string", "description": "Path to the mesh file"}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_get_mesh_info",
|
||||
description="Get detailed mesh information (vertices, faces, area, volume, is closed, etc.)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the mesh object"}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_mesh_from_points",
|
||||
description="Create a terrain mesh from a set of 3D points using Delaunay triangulation",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"points": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"type": "number"},
|
||||
"y": {"type": "number"},
|
||||
"z": {"type": "number"}
|
||||
},
|
||||
"required": ["x", "y", "z"]
|
||||
},
|
||||
"description": "List of 3D points for terrain mesh"
|
||||
}
|
||||
},
|
||||
"required": ["points"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_mesh_reduce",
|
||||
description="Reduce mesh polygon count while preserving shape",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the mesh"},
|
||||
"target_percent": {"type": "number", "description": "Target percentage of faces to keep (0-100)", "default": 50}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_mesh_smooth",
|
||||
description="Smooth a mesh surface",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the mesh"},
|
||||
"iterations": {"type": "integer", "description": "Number of smoothing iterations", "default": 1}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_mesh_contours",
|
||||
description="Create contour lines (elevation lines) from a terrain mesh",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the terrain mesh"},
|
||||
"interval": {"type": "number", "description": "Vertical interval between contours (e.g., 1.0 for 1m contours)"},
|
||||
"base_elevation": {"type": "number", "description": "Starting elevation", "default": 0}
|
||||
},
|
||||
"required": ["object_id", "interval"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_mesh_slope_analysis",
|
||||
description="Analyze slope/gradient of terrain mesh, returns min/max/average slope and optionally colors mesh by slope",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the terrain mesh"},
|
||||
"color_by_slope": {"type": "boolean", "description": "Color mesh faces by slope angle", "default": True}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_mesh_volume_between",
|
||||
description="Calculate volume between two meshes (cut/fill calculation for earthwork)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"mesh1_id": {"type": "string", "description": "GUID of first mesh (e.g., existing terrain)"},
|
||||
"mesh2_id": {"type": "string", "description": "GUID of second mesh (e.g., proposed terrain)"}
|
||||
},
|
||||
"required": ["mesh1_id", "mesh2_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_mesh_section",
|
||||
description="Create section/profile curves through a mesh at specified locations",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the mesh"},
|
||||
"plane_origin": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"type": "number"},
|
||||
"y": {"type": "number"},
|
||||
"z": {"type": "number", "default": 0}
|
||||
},
|
||||
"required": ["x", "y"]
|
||||
},
|
||||
"plane_normal": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"type": "number", "default": 0},
|
||||
"y": {"type": "number", "default": 1},
|
||||
"z": {"type": "number", "default": 0}
|
||||
},
|
||||
"description": "Normal vector of cutting plane"
|
||||
}
|
||||
},
|
||||
"required": ["object_id", "plane_origin"]
|
||||
}
|
||||
),
|
||||
|
||||
# === Point Cloud Tools ===
|
||||
Tool(
|
||||
name="rhino_import_pointcloud",
|
||||
description="Import a point cloud file (XYZ, PTS, E57, LAS, LAZ, PLY) into Rhino",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {"type": "string", "description": "Path to the point cloud file"}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_get_pointcloud_info",
|
||||
description="Get point cloud information (point count, bounding box, has colors, has normals)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the point cloud"}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_pointcloud_to_mesh",
|
||||
description="Convert point cloud to terrain mesh using Delaunay triangulation",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the point cloud"},
|
||||
"max_edge_length": {"type": "number", "description": "Maximum triangle edge length (filters out large triangles)", "default": 0}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_pointcloud_sample",
|
||||
description="Reduce point cloud by random or grid sampling",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the point cloud"},
|
||||
"method": {"type": "string", "enum": ["random", "grid"], "description": "Sampling method"},
|
||||
"target_count": {"type": "integer", "description": "Target number of points (for random sampling)"},
|
||||
"grid_size": {"type": "number", "description": "Grid cell size (for grid sampling)"}
|
||||
},
|
||||
"required": ["object_id", "method"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_pointcloud_crop",
|
||||
description="Crop point cloud to a boundary curve or box",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of the point cloud"},
|
||||
"boundary_id": {"type": "string", "description": "GUID of boundary curve (closed polyline) or box"}
|
||||
},
|
||||
"required": ["object_id", "boundary_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_pointcloud_elevation_at",
|
||||
description="Get elevation (Z value) at specific X,Y coordinates from point cloud or mesh",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of point cloud or mesh"},
|
||||
"x": {"type": "number", "description": "X coordinate"},
|
||||
"y": {"type": "number", "description": "Y coordinate"}
|
||||
},
|
||||
"required": ["object_id", "x", "y"]
|
||||
}
|
||||
),
|
||||
|
||||
# === Hydrology / Water Flow Tools ===
|
||||
Tool(
|
||||
name="rhino_terrain_flow_direction",
|
||||
description="Calculate water flow direction on terrain mesh (creates arrows showing drainage direction)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of terrain mesh"}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_terrain_watershed",
|
||||
description="Delineate watershed/catchment areas on terrain",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of terrain mesh"},
|
||||
"pour_point": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"type": "number"},
|
||||
"y": {"type": "number"}
|
||||
},
|
||||
"description": "Pour point (outlet) location"
|
||||
}
|
||||
},
|
||||
"required": ["object_id", "pour_point"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_terrain_low_points",
|
||||
description="Find low points/depressions in terrain where water would collect",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of terrain mesh"},
|
||||
"min_depth": {"type": "number", "description": "Minimum depression depth to report", "default": 0.1}
|
||||
},
|
||||
"required": ["object_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_create_drainage_line",
|
||||
description="Create a drainage/swale line following terrain low points between two points",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"terrain_id": {"type": "string", "description": "GUID of terrain mesh"},
|
||||
"start_point": {
|
||||
"type": "object",
|
||||
"properties": {"x": {"type": "number"}, "y": {"type": "number"}},
|
||||
"required": ["x", "y"]
|
||||
},
|
||||
"end_point": {
|
||||
"type": "object",
|
||||
"properties": {"x": {"type": "number"}, "y": {"type": "number"}},
|
||||
"required": ["x", "y"]
|
||||
}
|
||||
},
|
||||
"required": ["terrain_id", "start_point", "end_point"]
|
||||
}
|
||||
),
|
||||
|
||||
# === Road/Path Tools ===
|
||||
Tool(
|
||||
name="rhino_create_road_surface",
|
||||
description="Create a road surface on terrain from centerline curve with specified width and cross-slope",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"terrain_id": {"type": "string", "description": "GUID of terrain mesh"},
|
||||
"centerline_id": {"type": "string", "description": "GUID of road centerline curve"},
|
||||
"width": {"type": "number", "description": "Road width"},
|
||||
"cross_slope": {"type": "number", "description": "Cross slope percentage (e.g., 2 for 2%)", "default": 2}
|
||||
},
|
||||
"required": ["terrain_id", "centerline_id", "width"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_analyze_road_grades",
|
||||
description="Analyze longitudinal grades along a road/path curve on terrain",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"terrain_id": {"type": "string", "description": "GUID of terrain mesh"},
|
||||
"curve_id": {"type": "string", "description": "GUID of road/path curve"},
|
||||
"station_interval": {"type": "number", "description": "Interval for grade measurements", "default": 10}
|
||||
},
|
||||
"required": ["terrain_id", "curve_id"]
|
||||
}
|
||||
),
|
||||
|
||||
# === GIS Tools ===
|
||||
Tool(
|
||||
name="rhino_import_geotiff",
|
||||
description="Import a GeoTIFF DEM (Digital Elevation Model) file as a heightfield mesh",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {"type": "string", "description": "Path to the GeoTIFF file (.tif, .tiff)"}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_import_shapefile",
|
||||
description="Import an ESRI Shapefile (.shp) containing vector data (points, lines, polygons)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {"type": "string", "description": "Path to the shapefile (.shp)"},
|
||||
"layer_name": {"type": "string", "description": "Target layer name for imported geometry", "default": "Shapefile"}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_import_xyz",
|
||||
description="Import XYZ point data from a text file (CSV, TXT) as a point cloud",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"file_path": {"type": "string", "description": "Path to the XYZ file"},
|
||||
"delimiter": {"type": "string", "description": "Column delimiter (space, comma, tab)", "default": " "},
|
||||
"skip_lines": {"type": "integer", "description": "Number of header lines to skip", "default": 0}
|
||||
},
|
||||
"required": ["file_path"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_transform_coordinates",
|
||||
description="Transform/offset coordinates of objects (for coordinate system conversion or moving to origin)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of object to transform (optional - transforms all if not specified)"},
|
||||
"from_crs": {"type": "string", "description": "Source coordinate system (e.g., 'WGS84', 'UTM32N', 'EPSG:25832')"},
|
||||
"to_crs": {"type": "string", "description": "Target coordinate system"},
|
||||
"offset_x": {"type": "number", "description": "X offset to apply", "default": 0},
|
||||
"offset_y": {"type": "number", "description": "Y offset to apply", "default": 0},
|
||||
"offset_z": {"type": "number", "description": "Z offset to apply", "default": 0}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_get_bounding_box",
|
||||
description="Get the bounding box coordinates of an object or all objects (useful for coordinate reference)",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"object_id": {"type": "string", "description": "GUID of object (optional - gets bounds of all objects if not specified)"}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_create_grid",
|
||||
description="Create a survey/reference grid within a boundary with optional terrain elevation sampling",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"boundary_id": {"type": "string", "description": "GUID of boundary curve (optional)"},
|
||||
"cell_size": {"type": "number", "description": "Grid cell size", "default": 10},
|
||||
"elevation_source": {"type": "string", "description": "GUID of terrain mesh to sample elevations from (optional)"}
|
||||
},
|
||||
"required": []
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_create_site_section",
|
||||
description="Create a terrain section/profile between two points with elevation data",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"terrain_id": {"type": "string", "description": "GUID of terrain mesh"},
|
||||
"start_point": {
|
||||
"type": "object",
|
||||
"properties": {"x": {"type": "number"}, "y": {"type": "number"}},
|
||||
"required": ["x", "y"]
|
||||
},
|
||||
"end_point": {
|
||||
"type": "object",
|
||||
"properties": {"x": {"type": "number"}, "y": {"type": "number"}},
|
||||
"required": ["x", "y"]
|
||||
},
|
||||
"sample_interval": {"type": "number", "description": "Distance between sample points", "default": 1}
|
||||
},
|
||||
"required": ["terrain_id", "start_point", "end_point"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="rhino_calculate_area_volume",
|
||||
description="Calculate area and cut/fill volumes within a boundary relative to terrain",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"boundary_id": {"type": "string", "description": "GUID of closed boundary curve"},
|
||||
"terrain_id": {"type": "string", "description": "GUID of terrain mesh (optional for volume calculation)"},
|
||||
"reference_elevation": {"type": "number", "description": "Reference elevation for cut/fill calculation", "default": 0}
|
||||
},
|
||||
"required": ["boundary_id"]
|
||||
}
|
||||
),
|
||||
|
||||
# === Export Tools ===
|
||||
Tool(
|
||||
name="rhino_export",
|
||||
@@ -662,6 +1081,67 @@ async def list_tools() -> list[Tool]:
|
||||
"required": []
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="grasshopper_delete_component",
|
||||
description="Delete a component from the Grasshopper canvas",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"component_id": {"type": "string", "description": "GUID or name of the component to delete"}
|
||||
},
|
||||
"required": ["component_id"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="grasshopper_add_group",
|
||||
description="Add a group/comment box around components to organize and document the definition",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {"type": "string", "description": "Group name/label (appears as title)"},
|
||||
"component_ids": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"description": "List of component GUIDs to include in the group"
|
||||
},
|
||||
"color": {"type": "string", "description": "Group color as hex (e.g., '#FF9999' for light red)", "default": "#FFDDDDDD"}
|
||||
},
|
||||
"required": ["name"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="grasshopper_add_scribble",
|
||||
description="Add a text annotation/comment to the Grasshopper canvas",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"text": {"type": "string", "description": "Text content of the annotation"},
|
||||
"position": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"x": {"type": "number"},
|
||||
"y": {"type": "number"}
|
||||
},
|
||||
"required": ["x", "y"],
|
||||
"description": "Position on canvas"
|
||||
},
|
||||
"font_size": {"type": "integer", "description": "Font size in points", "default": 20}
|
||||
},
|
||||
"required": ["text", "position"]
|
||||
}
|
||||
),
|
||||
Tool(
|
||||
name="grasshopper_rename_component",
|
||||
description="Rename a component's nickname/label",
|
||||
inputSchema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"component_id": {"type": "string", "description": "GUID or name of the component"},
|
||||
"new_name": {"type": "string", "description": "New nickname for the component"}
|
||||
},
|
||||
"required": ["component_id", "new_name"]
|
||||
}
|
||||
),
|
||||
|
||||
# === Rhino Commands ===
|
||||
Tool(
|
||||
|
||||
@@ -64,6 +64,27 @@ namespace RhinoMCP
|
||||
// Grasshopper (delegated to GrasshopperHandler)
|
||||
var gh when gh.StartsWith("grasshopper_") => GrasshopperHandler.Execute(action, parameters),
|
||||
|
||||
// Mesh, Point Cloud, Terrain, GIS (delegated to MeshTerrainHandler)
|
||||
var mesh when mesh.StartsWith("rhino_mesh_") ||
|
||||
mesh.StartsWith("rhino_import_mesh") ||
|
||||
mesh.StartsWith("rhino_get_mesh_") ||
|
||||
mesh.StartsWith("rhino_pointcloud_") ||
|
||||
mesh.StartsWith("rhino_import_pointcloud") ||
|
||||
mesh.StartsWith("rhino_get_pointcloud_") ||
|
||||
mesh.StartsWith("rhino_terrain_") ||
|
||||
mesh.StartsWith("rhino_create_drainage_") ||
|
||||
mesh.StartsWith("rhino_create_road_") ||
|
||||
mesh.StartsWith("rhino_analyze_road_") ||
|
||||
mesh.StartsWith("rhino_import_geotiff") ||
|
||||
mesh.StartsWith("rhino_import_shapefile") ||
|
||||
mesh.StartsWith("rhino_import_xyz") ||
|
||||
mesh.StartsWith("rhino_transform_coordinates") ||
|
||||
mesh.StartsWith("rhino_get_bounding_box") ||
|
||||
mesh.StartsWith("rhino_create_grid") ||
|
||||
mesh.StartsWith("rhino_create_site_section") ||
|
||||
mesh.StartsWith("rhino_calculate_area_volume")
|
||||
=> MeshTerrainHandler.Execute(action, parameters, doc!),
|
||||
|
||||
_ => new { error = $"Unknown action: {action}" }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -31,6 +31,10 @@ namespace RhinoMCP
|
||||
"grasshopper_bake" => BakeGeometry(parameters),
|
||||
"grasshopper_save_definition" => SaveDefinition(parameters),
|
||||
"grasshopper_recompute" => Recompute(),
|
||||
"grasshopper_delete_component" => DeleteComponent(parameters),
|
||||
"grasshopper_add_group" => AddGroup(parameters),
|
||||
"grasshopper_add_scribble" => AddScribble(parameters),
|
||||
"grasshopper_rename_component" => RenameComponent(parameters),
|
||||
_ => new { error = $"Unknown Grasshopper action: {action}" }
|
||||
};
|
||||
}
|
||||
@@ -513,5 +517,159 @@ namespace RhinoMCP
|
||||
|
||||
return new { success = true, message = "Definition recomputed" };
|
||||
}
|
||||
|
||||
private static object DeleteComponent(JObject p)
|
||||
{
|
||||
var doc = GetActiveDocument();
|
||||
if (doc == null)
|
||||
return new { error = "No active Grasshopper document" };
|
||||
|
||||
var componentId = p["component_id"]?.ToString() ?? "";
|
||||
if (string.IsNullOrEmpty(componentId))
|
||||
return new { error = "Component ID required" };
|
||||
|
||||
var obj = FindComponent(doc, componentId);
|
||||
if (obj == null)
|
||||
return new { error = $"Component '{componentId}' not found" };
|
||||
|
||||
var name = obj.Name;
|
||||
var guid = obj.InstanceGuid.ToString();
|
||||
|
||||
doc.RemoveObject(obj, true);
|
||||
Instances.ActiveCanvas?.Refresh();
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
deleted_component = name,
|
||||
instance_guid = guid
|
||||
};
|
||||
}
|
||||
|
||||
private static object AddGroup(JObject p)
|
||||
{
|
||||
var doc = GetActiveDocument();
|
||||
if (doc == null)
|
||||
return new { error = "No active Grasshopper document" };
|
||||
|
||||
var name = p["name"]?.ToString() ?? "Group";
|
||||
var componentIds = p["component_ids"] as JArray;
|
||||
var colorHex = p["color"]?.ToString() ?? "#FFDDDDDD";
|
||||
|
||||
// Parse color
|
||||
Color groupColor;
|
||||
try
|
||||
{
|
||||
groupColor = ColorTranslator.FromHtml(colorHex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
groupColor = Color.FromArgb(255, 221, 221, 221);
|
||||
}
|
||||
|
||||
var group = new GH_Group();
|
||||
group.CreateAttributes();
|
||||
group.NickName = name;
|
||||
group.Colour = groupColor;
|
||||
|
||||
// Add components to group
|
||||
var addedCount = 0;
|
||||
if (componentIds != null)
|
||||
{
|
||||
foreach (var idToken in componentIds)
|
||||
{
|
||||
var id = idToken?.ToString();
|
||||
if (!string.IsNullOrEmpty(id))
|
||||
{
|
||||
var obj = FindComponent(doc, id);
|
||||
if (obj != null)
|
||||
{
|
||||
group.AddObject(obj.InstanceGuid);
|
||||
addedCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
doc.AddObject(group, false);
|
||||
group.ExpireCaches();
|
||||
Instances.ActiveCanvas?.Refresh();
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
group_name = name,
|
||||
instance_guid = group.InstanceGuid.ToString(),
|
||||
components_in_group = addedCount,
|
||||
color = colorHex
|
||||
};
|
||||
}
|
||||
|
||||
private static object AddScribble(JObject p)
|
||||
{
|
||||
var doc = GetActiveDocument();
|
||||
if (doc == null)
|
||||
return new { error = "No active Grasshopper document" };
|
||||
|
||||
var text = p["text"]?.ToString() ?? "Comment";
|
||||
var position = p["position"] as JObject;
|
||||
var x = position?["x"]?.Value<float>() ?? 0;
|
||||
var y = position?["y"]?.Value<float>() ?? 0;
|
||||
var fontSize = p["font_size"]?.Value<int>() ?? 20;
|
||||
|
||||
var scribble = new GH_Scribble();
|
||||
scribble.CreateAttributes();
|
||||
|
||||
// Set text and position
|
||||
scribble.Text = text;
|
||||
scribble.Font = new Font("Arial", fontSize);
|
||||
|
||||
// Position the scribble
|
||||
var bounds = new RectangleF(x, y, 200, 50);
|
||||
scribble.Attributes.Bounds = bounds;
|
||||
scribble.Attributes.Pivot = new PointF(x, y);
|
||||
|
||||
doc.AddObject(scribble, false);
|
||||
Instances.ActiveCanvas?.Refresh();
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
text,
|
||||
instance_guid = scribble.InstanceGuid.ToString(),
|
||||
position = new { x, y },
|
||||
font_size = fontSize
|
||||
};
|
||||
}
|
||||
|
||||
private static object RenameComponent(JObject p)
|
||||
{
|
||||
var doc = GetActiveDocument();
|
||||
if (doc == null)
|
||||
return new { error = "No active Grasshopper document" };
|
||||
|
||||
var componentId = p["component_id"]?.ToString() ?? "";
|
||||
var newName = p["new_name"]?.ToString() ?? "";
|
||||
|
||||
if (string.IsNullOrEmpty(componentId))
|
||||
return new { error = "Component ID required" };
|
||||
|
||||
var obj = FindComponent(doc, componentId);
|
||||
if (obj == null)
|
||||
return new { error = $"Component '{componentId}' not found" };
|
||||
|
||||
var oldName = obj.NickName;
|
||||
obj.NickName = newName;
|
||||
obj.ExpirePreview(true);
|
||||
Instances.ActiveCanvas?.Refresh();
|
||||
|
||||
return new
|
||||
{
|
||||
success = true,
|
||||
instance_guid = obj.InstanceGuid.ToString(),
|
||||
old_name = oldName,
|
||||
new_name = newName
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1508
src/rhino-plugin/RhinoMCP/MeshTerrainHandler.cs
Normal file
1508
src/rhino-plugin/RhinoMCP/MeshTerrainHandler.cs
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user