name: rhinomcp
description: Control Rhino 3D via RhinoMCP plugin. Use when creating, modifying, or deleting 3D geometry (spheres, boxes, cylinders, lines, curves, meshes), managing layers and materials (including PBR), boolean operations, transforms, file import/export, groups/blocks, or querying document information. Requires Rhino running on Windows with RhinoMCP plugin started (tcpstart command for WSL access).
RhinoMCP Skill
Control Rhino 3D directly from Clawdbot via TCP socket connection to the RhinoMCP plugin.
Prerequisites
- Rhino 7/8 running on Windows
- RhinoMCP plugin installed and built
- Plugin started: In Rhino command line, type
tcpstart(for WSL/remote access)
Note: Use
mcpstartfor local-only access (Cursor, Claude Desktop),tcpstartfor WSL/Clawdbot.
Configuration
Edit config.json:
{
"connection": {
"host": "172.31.96.1",
"port": 1999,
"timeout": 15.0
},
"screenshots": {
"linux_dir": "/home/mcmuff/clawd/projects/rhinomcp-dev/captures",
"windows_dir": "\\\\wsl.localhost\\Ubuntu\\home\\mcmuff\\clawd\\projects\\rhinomcp-dev\\captures"
}
}
Quick Test
cd ~/clawd/skills/rhinomcp/scripts
python3 rhino_client.py ping
Scripts Reference
| Script | Purpose |
|---|---|
rhino_client.py |
Base TCP client, raw commands |
geometry.py |
Create primitives (box, sphere, cylinder, curves...) |
transforms.py |
Move, rotate, scale, copy, mirror, arrays |
booleans.py |
Union, difference, intersection |
selection.py |
Select by layer, type, name, IDs |
analysis.py |
Object info, properties, bounding box, volume |
curves.py |
Offset, fillet, chamfer, join, explode |
surfaces.py |
Loft, extrude, revolve, sweep |
layers.py |
Create, set, list, delete layers |
materials.py |
PBR materials, assign to layers |
viewport.py |
Views, camera, screenshots |
render.py |
Lights, render settings, render to file |
files.py |
Open, save, import, export (STEP, OBJ, STL...) |
groups.py |
Groups and block definitions |
grasshopper.py |
Grasshopper Player automation (run/info/preset with custom params) |
presets.py |
Preset & Template manager for GH definitions |
scene.py |
Document info, batch operations |
🌿 Grasshopper Player Automation
Run Grasshopper definitions with custom parameters directly from CLI.
Basic Usage
# Show available parameters in a GH file
python3 grasshopper.py info "C:/path/to/definition.gh"
# Run with default parameters
python3 grasshopper.py run "C:/path/to/definition.gh"
# Run with custom parameters
python3 grasshopper.py run "C:/path/to/definition.gh" --Lichthoehe 2200 --Lichtbreite 1000
# Set insertion point
python3 grasshopper.py run "C:/path/to/definition.gh" --Point 100,200,0
Parameter Discovery
python3 grasshopper.py info "C:/path/to/Rahmentuer_UD4.gh"
# Output:
# Available Parameters (26):
# ----------------------------------------------------------
# --Lichthoehe = 2100.0 [1800.0 - 2600.0] (Number)
# --Lichtbreite = 900.0 [600.0 - 1400.0] (Number)
# --Rahmendicke = 53.0 (Number)
# --Tuerstaerke = 58.0 (Number)
# --DichtNut_Rahmen = True (Boolean)
# ...
Parameter Aliases
GH nicknames are automatically mapped to Player prompt names:
| GH Nickname | Maps to |
|---|---|
| Pt | Point |
| Punkt | Point |
| Position | Point |
| Pos | Point |
Add custom aliases in PARAM_ALIASES dict in grasshopper.py.
How It Works
infoloads the GH file via Grasshopper SDK to extract parameter metadata (names, types, defaults, min/max)runstarts Rhino's GrasshopperPlayer command viaSendKeystrokes(non-blocking)- Script monitors command prompts and sends parameter values
- Prompts like
Lichthoehe <2100>get the custom or default value Get Pointprompts receive the--Pointcoordinate or default0,0,0
Example: Create Multiple Doors
# Door 1: Standard at origin
python3 grasshopper.py run "C:/path/to/Rahmentuer_UD4.gh" \
--Lichthoehe 2100 --Lichtbreite 900 --Point 0,0,0
# Door 2: Wider door offset 1500mm
python3 grasshopper.py run "C:/path/to/Rahmentuer_UD4.gh" \
--Lichthoehe 2100 --Lichtbreite 1000 --Point 1500,0,0
# Door 3: Taller door offset 3000mm
python3 grasshopper.py run "C:/path/to/Rahmentuer_UD4.gh" \
--Lichthoehe 2300 --Lichtbreite 900 --Point 3000,0,0
Debugging
# Enable verbose logging
export RHINOMCP_DEBUG=1
python3 grasshopper.py run "C:/path/to/file.gh" --Lichthoehe 2200
🎯 Presets & Templates
Quick access to common Grasshopper configurations.
List Presets
python3 grasshopper.py presets
# Output:
# Available Presets (5):
# ------------------------------------------------------------
# standard_900 Standard Innentür 900mm
# standard_800 Standard Innentür 800mm
# brandschutz_t30 Brandschutztür T30 (EI30)
# nasszelle_750 Nasszelltür 750mm (WC/Bad)
# aussentuer_schwelle Aussentür mit Schwelle
Run a Preset
# Run with defaults
python3 grasshopper.py preset standard_900 --Point 0,0,0
# Run with parameter override
python3 grasshopper.py preset standard_900 --Lichthoehe 2200 --Point 1500,0,0
# Show preset details
python3 grasshopper.py preset standard_900 --info
# Validate only
python3 grasshopper.py preset brandschutz_t30 --validate
Templates
Templates map friendly names to GH files with defaults and validation:
python3 grasshopper.py templates
Adding Custom Presets
Edit config/presets.yaml to add your own:
presets:
my_custom_door:
description: "My custom door preset"
template: rahmentuer_ud4
params:
Lichtbreite: 1200
Lichthoehe: 2400
Templates support inheritance — see config/templates.yaml.
🎯 Geometry Creation
# Primitives
python3 geometry.py sphere --radius 5 --position 0,0,0 --name "Ball"
python3 geometry.py box --width 10 --length 10 --height 5 --color 255,0,0
python3 geometry.py cylinder --radius 2 --height 8 --layer "Parts"
python3 geometry.py cone --radius 3 --height 6
python3 geometry.py line --start 0,0,0 --end 10,10,0
python3 geometry.py circle --radius 5
python3 geometry.py arc --radius 5 --angle 90
python3 geometry.py polyline --points "0,0,0 10,0,0 10,10,0 0,10,0"
Supported Geometry Types
| Type | Key Parameters |
|---|---|
| POINT | location |
| LINE | start, end |
| POLYLINE | points |
| CIRCLE | center, radius |
| ARC | center, radius, angle |
| ELLIPSE | center, radius_x, radius_y |
| CURVE | points, degree |
| BOX | width, length, height |
| SPHERE | radius |
| CONE | radius, height |
| CYLINDER | radius, height |
| MESH | vertices, faces |
🔄 Transform Operations
# Move object
python3 transforms.py move <id> --vector 10,0,0
# Rotate object (degrees around axis through point)
python3 transforms.py rotate <id> --angle 45 --axis 0,0,1 --center 0,0,0
# Scale object
python3 transforms.py scale <id> --factor 2.0 --center 0,0,0
# Copy with offset
python3 transforms.py copy <id> --offset 10,0,0
# Mirror across plane
python3 transforms.py mirror <id> --origin 0,0,0 --normal 1,0,0
# Linear array: 5 copies along X
python3 transforms.py linear <id> --direction 1,0,0 --count 5 --distance 10
# Polar array: 8 copies around Z axis
python3 transforms.py polar <id> --center 0,0,0 --axis 0,0,1 --count 8
⚡ Boolean Operations
# Union multiple solids
python3 booleans.py union <id1> <id2> <id3>
# Difference: subtract cutter(s) from base
python3 booleans.py difference <base_id> <cutter_id>
# Intersection
python3 booleans.py intersection <id1> <id2>
# Keep input objects (don't delete)
python3 booleans.py union <id1> <id2> --keep
Note: Objects must be closed solids (Breps).
🎯 Selection
# Select all
python3 selection.py all
# Clear selection
python3 selection.py none
# Get info about selected objects
python3 selection.py get
# Select by layer
python3 selection.py layer "MyLayer"
# Select by object type
python3 selection.py type solid # solid, curve, surface, mesh, point, etc.
# Select by name (partial match)
python3 selection.py name "Box"
# Select specific IDs
python3 selection.py ids <id1> <id2> <id3>
# Combined filters
python3 selection.py filter --layer "Parts" --type solid
📊 Object Analysis
# Basic object info
python3 analysis.py info <object_id>
# Detailed properties (bounding box, area, volume, centroid)
python3 analysis.py properties <object_id>
# Info about selected objects
python3 analysis.py selected
# Document summary
python3 analysis.py document
〰️ Curve Operations
# Offset curve
python3 curves.py offset <curve_id> --distance 5
# Fillet two curves
python3 curves.py fillet <curve1_id> <curve2_id> --radius 2
# Chamfer two curves
python3 curves.py chamfer <curve1_id> <curve2_id> --distance 3
# Join curves into polycurve
python3 curves.py join <id1> <id2> <id3>
# Explode polycurve into segments
python3 curves.py explode <polycurve_id>
# Keep input (don't delete)
python3 curves.py join <id1> <id2> --keep
🏔️ Surface Operations
# Loft through curves
python3 surfaces.py loft <curve1_id> <curve2_id> <curve3_id>
# Extrude curve along vector
python3 surfaces.py extrude <curve_id> --direction 0,0,10
# Revolve curve around axis
python3 surfaces.py revolve <curve_id> --axis-start 0,0,0 --axis-end 0,0,1 --angle 360
# Sweep curve along rail
python3 surfaces.py sweep <profile_id> <rail_id>
# Create planar surface from closed curve
python3 surfaces.py planar <closed_curve_id>
📁 Layers
python3 layers.py create "MyLayer" --color 255,100,100
python3 layers.py set "MyLayer"
python3 layers.py list
python3 layers.py delete "OldLayer"
🎨 Materials (PBR)
# Metal presets
python3 materials.py preset gold
python3 materials.py preset silver
python3 materials.py preset copper
# Custom PBR material
python3 materials.py pbr "Chrome" --color 200,200,210 --metallic 0.95 --roughness 0.02
# Assign material to layer
python3 materials.py assign "MyLayer" <material_id>
📷 Viewport & Screenshots
# Set standard view
python3 viewport.py view Perspective
python3 viewport.py view Top
# Zoom to fit all
python3 viewport.py zoom
# Zoom to selection
python3 viewport.py zoom --selected
# Orbit camera
python3 viewport.py orbit --yaw 45 --pitch 30
# Set camera position
python3 viewport.py camera --position 100,100,50 --target 0,0,0 --lens 35
# Capture screenshot (saves to linux_dir, returns linux_path)
python3 viewport.py screenshot --width 1920 --height 1080
python3 viewport.py screenshot --output myrender.png
# Render with materials
python3 viewport.py render --output render.png
Screenshots are saved directly to the Linux filesystem via WSL UNC path. The returned
linux_pathcan be read directly.
💡 Render & Lighting
# Set render quality
python3 render.py settings --width 1920 --height 1080 --quality high
python3 render.py settings --background 50,50,50
# Add lights
python3 render.py light point --position 50,50,100 --intensity 1.5
python3 render.py light directional --direction -1,-1,-1
python3 render.py light spot --position 0,0,100 --target 0,0,0
# Render to file
python3 render.py render --output scene.png
📦 Files (Import/Export)
# Open 3DM file
python3 files.py open "/path/to/file.3dm"
# Save current document
python3 files.py save
python3 files.py save --path "/path/to/new.3dm"
# Export to various formats
python3 files.py export output.step
python3 files.py export output.obj --ids <id1> <id2>
python3 files.py export output.stl --format stl
# Import mesh
python3 files.py import model.obj
Supported Export Formats
STEP, IGES, OBJ, STL, DXF, DWG, 3DS, FBX, DAE
📦 Groups & Blocks
# Create group
python3 groups.py group <id1> <id2> --name "MyGroup"
# Ungroup
python3 groups.py ungroup --name "MyGroup"
# Create block definition
python3 groups.py block-create "MyBlock" <id1> <id2> --base 0,0,0
# Insert block instance
python3 groups.py block-insert "MyBlock" --position 10,0,0 --scale 2 --rotation 45
# Explode block
python3 groups.py block-explode <instance_id>
📜 RhinoScript Execution
# Execute inline code
python3 script_exec.py -c "import rhinoscriptsyntax as rs; rs.AddSphere([0,0,0], 10)"
# Execute script file
python3 script_exec.py -f ~/scripts/my_script.py
🔍 Log Monitoring
Check the Rhino log for debugging:
# View recent log entries
tail -30 "/mnt/c/Users/Adi.Muff/AppData/Local/Temp/rhinomcp.log"
# Continuous monitoring
tail -f "/mnt/c/Users/Adi.Muff/AppData/Local/Temp/rhinomcp.log" &
Example: Complete PBR Scene Workflow
# 1. Create layer with material
python3 layers.py create "Gold_Parts" --color 255,215,0
python3 materials.py preset gold
# → Note material_id
# 2. Assign material to layer
python3 materials.py assign "Gold_Parts" <material_id>
python3 layers.py set "Gold_Parts"
# 3. Create geometry
python3 geometry.py sphere --radius 5 --name "Gold_Ball"
python3 geometry.py box --width 10 --length 10 --height 2 --position 0,0,-3
# 4. Boolean difference (cut hole)
python3 geometry.py cylinder --radius 2 --height 5 --position 0,0,-3
python3 booleans.py difference <box_id> <cylinder_id>
# 5. Set camera and capture
python3 viewport.py camera --position 30,30,20 --target 0,0,0 --lens 35
python3 viewport.py screenshot --width 1920 --height 1080
# → Returns linux_path, read directly with Read tool
Troubleshooting
| Problem | Solution |
|---|---|
| Connection refused | Run tcpstart in Rhino |
| Timeout | Increase timeout in config.json |
| Boolean failed | Ensure objects are closed solids |
| Screenshot path issues | Check windows_dir UNC path in config |
| Command not found | Rebuild plugin after C# changes |