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:
architeur
2025-12-29 22:44:43 +01:00
parent f9d6715056
commit 873fba3f76
6 changed files with 2326 additions and 0 deletions

139
CLAUDE.md Normal file
View 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
View 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

View File

@@ -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 === # === Export Tools ===
Tool( Tool(
name="rhino_export", name="rhino_export",
@@ -662,6 +1081,67 @@ async def list_tools() -> list[Tool]:
"required": [] "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 === # === Rhino Commands ===
Tool( Tool(

View File

@@ -64,6 +64,27 @@ namespace RhinoMCP
// Grasshopper (delegated to GrasshopperHandler) // Grasshopper (delegated to GrasshopperHandler)
var gh when gh.StartsWith("grasshopper_") => GrasshopperHandler.Execute(action, parameters), 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}" } _ => new { error = $"Unknown action: {action}" }
}; };
} }

View File

@@ -31,6 +31,10 @@ namespace RhinoMCP
"grasshopper_bake" => BakeGeometry(parameters), "grasshopper_bake" => BakeGeometry(parameters),
"grasshopper_save_definition" => SaveDefinition(parameters), "grasshopper_save_definition" => SaveDefinition(parameters),
"grasshopper_recompute" => Recompute(), "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}" } _ => new { error = $"Unknown Grasshopper action: {action}" }
}; };
} }
@@ -513,5 +517,159 @@ namespace RhinoMCP
return new { success = true, message = "Definition recomputed" }; 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
};
}
} }
} }

File diff suppressed because it is too large Load Diff