rhinoclaw-x

内容来源:clawhub · 原始地址 · 查看安装指南

原始内容


name: rhinoclaw version: 0.7.2 description: Control Rhino 3D via the RhinoClaw plugin over TCP. Use whenever you need to create, modify, query, measure, or render 3D geometry, manage layers/materials, run Boolean and transform ops, drive Grasshopper definitions, work with VisualARQ BIM objects, or batch multiple steps as one atomic operation. Requires Rhino 7/8 running with tcpstart (the plugin's command line server) on the Rhino host.

RhinoClaw Skill

Direct TCP control of Rhino 3D from an OpenClaw agent. Plugin is running on a Windows or macOS Rhino instance; this skill talks to it from WSL / Linux / wherever you live.

Step 0 — ALWAYS run preflight first. One call returns the complete connection/auth state and an exact next_action. Only call other tools when data.auth == "ready". Then read § "Decision Tree" to pick the cheapest tool.


0. Preflight & error-code playbook (read before touching auth)

Never trial-and-error the connection. Run preflight (MCP) — or, on the raw TCP path, hello then a single ping — and act on the result. hello is auth-free: it reports the server's state even with no/wrong token and even while blocked, without tripping the brute-force counter.

preflight.data.auth / error_code Meaning Do exactly this
ready Connected, auth ok Proceed.
missing_client_token / AUTH_REQUIRED (no token) Plugin wants a token, client sends none Set RHINOCLAW_AUTH_TOKEN on the client (same value as the plugin). Do NOT retry blindly. Using the deployed skill? Re-sync it: scripts/sync-skill.sh.
token_mismatch / AUTH_REQUIRED (wrong token) Both sides have a token, they differ Make RHINOCLAW_AUTH_TOKEN identical on both sides, then restart Rhino (the plugin reads the token only at start).
blocked / AUTH_BLOCKED Too many failed auth attempts Wait until blocked_until. Do NOT retry — retries extend the cooldown.
not connected / CONNECTION_REFUSED Server not running / wrong host:port Open Rhino, run tcpstart (remote) or mcpstart (local). On WSL the Windows host IP changes per session — check RHINOCLAW_HOST.
TIMEOUT Command reached Rhino but blocked the UI thread / too slow Command-specific — pass a larger timeout. Not an auth problem; do not re-auth.

Rules of thumb:

  • Auth errors are configuration errors, not transient — never loop-retry them.
  • The plugin reads RHINOCLAW_AUTH_TOKEN only at start — changed the env after Rhino was running? Restart Rhino (or the RhinoClaw server).
  • One source of truth: ~/projects/RhinoClaw. The deployed skill at ~/clawd/skills/rhinoclaw is generated by scripts/sync-skill.sh — if preflight or hello look stale, re-sync.

Decision Tree — pick the cheapest tool that works

        ┌──────────────────────────────────┐
        │  What do you need to do?         │
        └──────────────────────────────────┘
                       │
   ┌───────────────────┼───────────────────┐
   ▼                   ▼                   ▼
 single op          many ops at once    inspect / measure
   │                   │                   │
   ▼                   ▼                   ▼
 typed module      batch_operations    scene_summary
 (geometry,        with rollback       find_nearby
  transforms,                          get_relationships
  booleans,                            get_object_info
  layers,
  materials,
  visualarq…)

Preference order — always try in this order, drop one level only when the previous can't express what you need:

  1. Typed Python helper in scripts/rhinoclaw_client/<module>.py (geometry.create_box, booleans.union, transforms.copy, …). Validated, undo-aware, never executes arbitrary code.
  2. batch_operations when you need ≥2 steps in one atomic shot (rollback / abort / continue / best-effort policies).
  3. script_exec.run_python with import rhinoscriptsyntax as rs (rs.AddSphere, rs.LayerNames, …). Use when no typed helper exists. RhinoScript is well documented and agent-friendly.
  4. RhinoCommon directly via script_exec.run_python. Use when rhinoscriptsyntax doesn't expose what you need. Cheatsheet at docs/rhinocommon-cookbook.md in the dev repo.
  5. run_native_command — fires RhinoApp.RunScript("_Loft …"). Last-resort hatch for commands that have no RhinoCommon equivalent (or where the native one is much simpler). Allowlisted by the plugin.

If you don't know what tools exist, call list_capabilities once at session start. It returns a categorised inventory of typed tools, helpers, native-command allowlist, and RhinoCommon cookbook topics.


Prerequisites

  1. Rhino 7/8 running on Windows (or macOS).
  2. RhinoClaw plugin ≥ 0.2.8 installed.
  3. Plugin server started:
    • mcpstart for local-only AI clients (Cursor / Claude Desktop).
    • tcpstart for remote / WSL / raw-TCP clients.
  4. Auth token (recommended for tcpstart): set RHINOCLAW_AUTH_TOKEN on both the Rhino host and the client side. See SECURITY.md and scripts/setup-auth-token.ps1 in the repo.

Configuration

config.json next to the scripts:

{
  "connection": {
    "host": "172.31.96.1",
    "port": 1999,
    "timeout": 15.0
  }
}

All four fields can be overridden by env vars: RHINOCLAW_HOST, RHINOCLAW_PORT, RHINOCLAW_TIMEOUT, RHINOCLAW_AUTH_TOKEN. The rhino_client.py module attaches the auth token to every TCP command automatically — agent never has to manage it manually.


Quick Test — run preflight first

cd ~/clawd/skills/rhinoclaw/scripts
python3 rhino_client.py preflight   # one call → connection/auth state + next_action
# → {"connected": true, "auth": "ready", "plugin_version": "0.5.0.0", ...}

Act only when "auth": "ready". Otherwise follow next_action exactly (see §0). python3 rhino_client.py hello shows the raw auth-free handshake; python3 rhino_client.py ping is the authenticated liveness check.

TCP = MCP parity — what raw TCP can do

The raw client reaches every plugin command via the generic send_command(cmd_type, params) — the same capabilities as the MCP server:

from rhino_client import RhinoClient
with RhinoClient() as c:
    c.preflight()                                   # connection/auth verdict
    c.send_command("create_object", {"type": "BOX", "params": {...}})
    c.send_command("build_and_bake_gh", {...})      # author + solve + bake a .gh
    c.build_and_bake_recipe("box", "C:/tmp/b.gh",   # native parametric recipe
                            params={"x": 40, "y": 20, "z": 10}, layer="Boxes")
    c.build_and_bake_recipe("list")                 # list recipes

Client-side high-level helpers mirror the MCP tools: preflight(), hello(), build_and_bake_recipe(). Everything else is a direct send_command (the plugin dispatches it). Recipes live in the plugin, so MCP and TCP behave identically. Requires a plugin build with these commands (0.5.0+ after rebuild).


Cookbook — common workflows

Five concrete recipes. Adapt freely; always prefer typed helpers over inline Python where possible.

Recipe 1 — "Create a layered box with a material" (atomic)

from rhino_client import RhinoClient
client = RhinoClient(); client.connect()

result = client.send_command("batch_operations", {
    "name": "Layered box",
    "on_error": "rollback",
    "steps": [
        {"tool": "create_layer",
         "args": {"name": "Walls", "color": [180, 180, 180]}},
        {"tool": "create_material",
         "args": {"name": "Concrete", "color": [200, 200, 200]}},
        {"tool": "assign_material_to_layer",
         "args": {"layer_name": "Walls", "material_id": 0}},
        {"tool": "create_object",
         "args": {"type": "BOX", "layer": "Walls",
                  "params": {"width": 5, "length": 3, "height": 2.5}}},
    ],
})

Recipe 2 — "Find every object near origin and inspect relationships"

nearby = client.send_command("find_nearby", {
    "point": [0, 0, 0], "radius": 100, "layer": "Walls",
})
for wall in nearby["results"]:
    relations = client.send_command("get_relationships", {
        "object_id": wall["id"],
        "touch_tolerance": 0.05,
    })
    print(wall["name"], "touches", len(relations["touching"]), "objects")

Recipe 3 — "Boolean a hole through a wall, leave geometry on failure"

# `abort` policy: stop on first failure but keep completed steps. Lets
# the user inspect/fix manually instead of an automatic rollback.
client.send_command("batch_operations", {
    "on_error": "abort",
    "steps": [
        {"tool": "create_object", "args": {"type": "CYLINDER", "params": {...}}},
        {"tool": "boolean_operation",
         "args": {"operation": "difference",
                  "primary_id": "<wall>", "secondary_ids": ["<cylinder>"]}},
    ],
})

Recipe 4 — "Run a Grasshopper definition with the file's own defaults"

# Inspect first to discover what the GH file actually wants — never guess.
info = client.send_command("inspect_grasshopper_definition", {
    "file_path": r"C:\path\to\Door.gh",
    "only_player_inputs": True,
})
print("Inputs:", [i["name"] for i in info["inputs"]])

# Run with the defaults the GH file already exposed:
client.send_command("run_grasshopper_interactive", {
    "file_path": r"C:\path\to\Door.gh",
    "inputs": {i["name"]: i.get("default")
               for i in info["inputs"] if i.get("default") is not None},
})

Recipe 5 — "Use a native Rhino command when nothing else fits"

# rhinoscriptsyntax has no clean Sweep1 wrapper — use the underlying
# native Rhino command. Plugin allowlists known-safe commands.
client.send_command("run_native_command", {
    "command": "_Sweep1",
    "args": ["_Pause", "_Pause", "_Enter"],
})

RhinoCommon and rhinoscriptsyntax — when typed tools don't reach

When you need something no typed RhinoClaw tool covers, you have two scripting paths via script_exec.run_python:

Option A — rhinoscriptsyntax (start here)

Friendlier API, well documented, almost-Rhino-Python-Console-equivalent. Find functions:

  • Online: https://developer.rhino3d.com/guides/rhinopython/
  • Indexed locally: get_rhinoscript_python_function_names(["surface", "curve"]) returns matching function names.
  • get_rhinoscript_python_code_guide("AddLoftSrf") returns the parameter signature + example.

Example: get all object IDs on a layer

client.send_command("execute_rhinoscript_python_code", {
    "code": "import rhinoscriptsyntax as rs; print(rs.ObjectsByLayer('Walls'))",
})

Option B — RhinoCommon directly

Mächtiger, stricter API. Use the cheatsheet at docs/rhinocommon-cookbook.md in the dev repo for hot-path patterns (Brep ops, Curve math, Mesh conversion, Transform composition, Intersection events, …).

When you need a function not in the cookbook:

  1. Search https://developer.rhino3d.com/api/rhinocommon/?searchtext=
  2. Note the namespace — most common: Rhino.Geometry, Rhino.DocObjects, Rhino.Display, Rhino.Render.
  3. Write a tiny test via execute_python3_code BEFORE wiring into a long pipeline.

Example (mesh from Brep):

client.send_command("execute_python3_code", {
    "code": """
import scriptcontext as sc
import System
from Rhino.Geometry import Mesh, MeshingParameters
brep = sc.doc.Objects.Find(System.Guid.Parse('<id>')).Geometry
meshes = Mesh.CreateFromBrep(brep, MeshingParameters.Default)
print('mesh count:', len(meshes))
""",
})

Negative examples — what NOT to do

  • rs.command("_Move ...") — interactive command, blocks the server. Use the typed move_object / transforms.move instead, or run_native_command for non-interactive scripts.
  • ❌ Many separate create_object calls when batch_operations works. TCP latency dominates; batching is 10× faster on Tailscale.
  • execute_rhinoscript_python_code for a single primitive — that's what the typed tools are for. Save scripting for when typed tools can't express the operation.
  • ❌ Guessing at GH parameter names. Always inspect_grasshopper_definition(only_player_inputs=True) first.
  • ❌ Acting on object IDs you didn't just receive. If unsure, call get_object_info or find_nearby first — the user might have deleted, locked, or hidden things since your last call.

Scripts Reference

Script Purpose
rhino_client.py Base TCP client; auto-attached auth token + retry
geometry.py Primitives (box, sphere, cylinder, point, line, curve, …)
transforms.py Move, rotate, scale, copy, mirror, arrays
booleans.py Union, difference, intersection
solids.py Solid-specific ops (extrude, fillet edges, shell)
surfaces.py Loft, sweep, revolve, patches
curves.py Offset, fillet, chamfer, join, explode
selection.py Select / list by layer, type, name, IDs
analysis.py Object info, properties, bounding box, volume, distance
scene.py Document-level info, scene summary, find_nearby
layers.py Create / set / list / delete / lock / hide layers
materials.py PBR materials, assignments
viewport.py Views, camera, screenshots
render.py Lights, render settings, render-to-file
groups.py Create / ungroup / list groups
objects.py Generic object ops (delete, rename, recolor, layer change)
text.py Text, text dots, leaders, annotations
files.py Open / save / export / import
grasshopper.py Run / inspect / build / bake GH definitions
script_exec.py Inline Python (rhinoscriptsyntax + RhinoCommon)
visualarq.py VisualARQ BIM objects (walls / doors / windows / IFC)
presets.py Reusable parameter sets
utils.py Common helpers (logging, validation)

Configuration files

  • config.json — runtime defaults (host, port, timeout, …).
  • config/presets.yaml — domain presets (door types, wall thicknesses).
  • config/templates.yaml — full pipeline templates.
  • references/commands.md — quick command lookup.

Troubleshooting

Symptom First check
AUTH_REQUIRED echo $RHINOCLAW_AUTH_TOKEN matches the value on the Windows side? Both sides restarted after setting the env var?
Connection refused tcpstart already typed in Rhino's command line? Plugin loaded? Try mcpstatus.
Connection timed out RHINOCLAW_HOST correct? On WSL the Windows host IP changes per session. ip route show | awk '/default/ {print $3}' from WSL gives current value.
AddressAlreadyInUse (port 1999) Old Rhino zombie. Get-NetTCPConnection -LocalPort 1999 in PowerShell on the host.
Tool returns success but nothing visible Likely on a hidden layer or off-screen. Run scene_summary + zoom_extents.
Long script "hangs" cancel_rhino_command() is the last resort. Default timeout is 15s; pass timeout=60 for long ones. Server-side max is RHINOCLAW_MAX_TIMEOUT=120.

Reference