---
title: "Rhino modeling script writer"
type: "skill"
url: "https://useaiblocks.com/skills/write-a-rhino-modeling-script"
updated: "2026-10-28"
first_published: "2026-10-25"
job: "Design things people love"
tested: true
licence: "MIT"
source: "https://github.com/github/awesome-copilot/tree/613efc8c21d9b3ce642dff361d17006cd9967e28/skills/rhino3d-scripts"
instructions: "https://useaiblocks.com/skills/write-a-rhino-modeling-script/SKILL.md"
---

# Rhino modeling script writer

> Share your Rhino task, version and model details. Get a script with steps to check it.

Share your Rhino version, task and source script. Choose the right API and guard each change. Get code and steps to check it.

Free skill for the job "Design things people love" · Level: intermediate · Thinking: medium · Tested · Updated 2026-10-28

## Links

- Page: https://useaiblocks.com/skills/write-a-rhino-modeling-script
- Instructions (SKILL.md): https://useaiblocks.com/skills/write-a-rhino-modeling-script/SKILL.md
- All files (zip): https://useaiblocks.com/skills/write-a-rhino-modeling-script.zip
- JSON version: https://useaiblocks.com/skills/write-a-rhino-modeling-script.json
- In your AI: connect https://useaiblocks.com/mcp and ask "Use AI Blocks to get write-a-rhino-modeling-script"

## What it does

Share your Rhino version, task and source script. Choose the right API and guard each change. Get code and steps to check it.

## What you give it

- The Rhino version, modeling task, document units, input geometry and any existing script.

## What you get

- A script file in the language that fits your task, ready to paste into Rhino's Script Editor.
- The exact way to run it, by Script Editor steps or a run command.
- A short list of checks to do in Rhino to confirm it worked.
- A plain note on what was not tested, since your AI cannot run Rhino.

## Example: what goes in and what comes out

### What goes in

- Your Rhino version
- What you want the script to do
- Your document units and the geometry it works on
- Your existing script, if you have one (optional)

### Before you start

- Rhino 7 or later, with Rhino 8 best. The skill is written for Rhino 8 (one Script Editor for Python 3, VB and C#). On Rhino 7, use the older Python and VBScript editors and expect some features to differ.
- Rhino's Script Editor to run the result

### What comes out

- **Script choice**: Which type fits your task: Python, VBScript, C# or a command macro, and why.
- **Script code**: The full script, with checks on picked objects and your document's tolerance.
- **Redraw and undo handling**: Code that pauses redraw, groups changes into one undo step, and restores the view if something fails.
- **How to run it**: Steps to open the Script Editor or run the file by name, or set up a toolbar button.
- **Checks to perform**: What to look for in Rhino to confirm the script did the right thing.
- **Verification limits**: A plain note that the script was not run in Rhino, and what that leaves unchecked.

## Use it when

- You want a new Rhino script for a modeling task, such as editing many objects at once.
- A script you have fails, freezes Rhino, or undoes only one object at a time.
- You need to share a script with a teammate or run it when Rhino starts.
- Not for a fixed list of commands with no variables (use a command macro) or for non-Rhino CAD work.

## Skip it when

- You only need a fixed run of commands with no variables or loops: use a Rhino command macro instead.
- You work in other CAD software, not Rhino: use that program's own scripting help.
- You need proof the script runs in Rhino: run it yourself, because your AI cannot test it for you.

## How to use it

1. Open Claude, ChatGPT or Gemini and paste in the skill text.
2. Tell your AI your Rhino version, your task, your document units and what geometry the script touches.
3. Paste your current script if you have one.
4. Save a copy of your Rhino file first, since the script changes objects in it. Then copy the code into Rhino's Script Editor and run it with the steps your AI gives you.
5. Do the listed checks in Rhino, then send any error text back to your AI to fix.
6. Next, once it runs, save the script in a folder listed under Options, Files, Search paths. Then add a toolbar button or alias with the macro ! _-RunPythonScript "MyScript.py" (Python), or add the file to the Startup list under Tools, Options to load it every session.

## What to do next

- [Small usability test planner](https://useaiblocks.com/skills/small-usability-test-planner): Design it

## Sources and credits

- From github: https://github.com/github/awesome-copilot/tree/613efc8c21d9b3ce642dff361d17006cd9967e28/skills/rhino3d-scripts, shared under the MIT licence (original name: rhino3d-scripts).
- [rhino3d-scripts skill](https://github.com/github/awesome-copilot/tree/613efc8c21d9b3ce642dff361d17006cd9967e28/skills/rhino3d-scripts), github: Original instructions and supporting files, with attribution and the MIT licence. Display copy describes the task.

## Instructions (SKILL.md)

## Rhino 3D Scripting Skill

Write production-quality scripts for Rhinoceros 3D. Covers the three scripting surfaces (RhinoScript/VBScript, RhinoPython, direct RhinoCommon .NET) and the Rhino 8+ Script Editor.

<!-- needs: written by tools/add-needs.mjs for AI Blocks; not part of the original skill. Edit that tool, not this section. -->
### Before you start
Use the material below to do this job. If facts or tool access are missing, explain what you can finish and what must wait.

**What you need** (use your own material or the linked skill):
- **Your question** (yours): the decision or problem, and what a good outcome looks like.
- **A codebase** (yours): a project's code, in a folder or repo the AI can open.

How to start:
1. **Look first.** Use what they've already attached, pasted or put in their about-me note. In a coding assistant with file access, look in the project too for these by name.
2. **Check what's missing.** Look for your question, a codebase. They can attach files, paste the details, or share a file path your assistant can read. Ask only for missing facts that change the result. If they ask you to start with what they have, complete the parts you can and mark the gaps.
3. **Do the job.** Follow the skill below with the facts and tools you have. Mark each gap with `MISSING:` where it belongs and never invent a fact to fill it.
<!-- /needs -->

### When to Use This Skill

- User asks to write, edit, or debug a `.rvb`, `.vbs`, or `.py` Rhino script
- User wants a Rhino **command macro** or wants to automate a sequence of Rhino commands
- User wants to manipulate geometry, layers, blocks, materials, viewports, or annotations from code
- User mentions `rhinoscriptsyntax`, `scriptcontext`, `RhinoCommon`, `Rhino.Geometry`, `RhinoDoc`, or the Script Editor
- User wants to pick objects, prompt for input, or build a small UI inside Rhino
- User asks how to load, run, or distribute a script (startup scripts, aliases, toolbar buttons)

### Choosing a Scripting Surface

Pick the surface based on the task, not preference. Recommend Python by default for new work.

| Surface | When to choose | File ext |
|---|---|---|
| **RhinoPython** (`rhinoscriptsyntax` + RhinoCommon) | Default for new scripts. Best ecosystem, readable, full RhinoCommon access. | `.py` |
| **RhinoScript** (VBScript) | Maintaining legacy `.rvb`/`.vbs` files; integrating with VBA/COM. | `.rvb`, `.vbs` |
| **RhinoCommon (C#/.NET) via Script Editor** | Performance-critical loops, complex geometry, leveraging .NET libraries. | `.cs` |
| **Command macro** | Pure sequence of existing Rhino commands; no logic. | toolbar/alias |

A macro is **not** a script — it is a string of command-line input (e.g. `! _-Line 0,0,0 10,0,0 _Enter`). Use a script the moment you need a variable, loop, or conditional.

### Prerequisites

- Rhino 7 or later (Rhino 8 strongly recommended — unified Script Editor supports Python 3, VB, and C# in one window).
- Script Editor: type `_ScriptEditor` (Rhino 8) or `_EditPythonScript` / `_EditScript` (older).
- Run a saved file from the command line with `_-RunPythonScript` or `_LoadScript` + `_RunScript`.

### Core Patterns

#### Python: minimal scaffold

```python
import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

def main():
    obj_id = rs.GetObject("Select a curve", filter=rs.filter.curve, preselect=True)
    if not obj_id:
        return
    length = rs.CurveLength(obj_id)
    print("Length: {0:.4f}".format(length))

if __name__ == "__main__":
    main()
```

#### Python: working with RhinoCommon directly

```python
import Rhino
import scriptcontext as sc

doc = sc.doc  # Rhino.RhinoDoc.ActiveDoc
tol = doc.ModelAbsoluteTolerance

circle = Rhino.Geometry.Circle(Rhino.Geometry.Point3d(0, 0, 0), 5.0)
curve_id = doc.Objects.AddCircle(circle)
doc.Views.Redraw()
```

#### VBScript: minimal scaffold

```vbscript
Option Explicit

Call Main()

Sub Main()
    Dim strObject
    strObject = Rhino.GetObject("Select a curve", 4)  ' 4 = curve filter
    If IsNull(strObject) Then Exit Sub
    Rhino.Print "Length: " & Rhino.CurveLength(strObject)
End Sub
```

#### Picking objects with a custom filter (Python, RhinoCommon)

```python
import Rhino
import scriptcontext as sc

go = Rhino.Input.Custom.GetObject()
go.SetCommandPrompt("Select breps")
go.GeometryFilter = Rhino.DocObjects.ObjectType.Brep
go.SubObjectSelect = False
go.GetMultiple(1, 0)
if go.CommandResult() != Rhino.Commands.Result.Success:
    pass
else:
    ids = [go.Object(i).ObjectId for i in range(go.ObjectCount)]
```

### Step-by-Step Workflows

#### Bulk-modify many objects fast

1. Disable redraw: `rs.EnableRedraw(False)`.
2. Wrap mutations in a single undo record: `undo = doc.BeginUndoRecord("My Op")` … `doc.EndUndoRecord(undo)`.
3. Use RhinoCommon directly inside the loop (skip `rhinoscriptsyntax` overhead).
4. Re-enable redraw and call `doc.Views.Redraw()` in a `try`/`finally` so a crash never leaves the viewport frozen.

#### Distribute a script to a teammate

1. Save the `.py` / `.rvb` somewhere on disk.
2. Add the folder to `Options → Files → Search paths` so Rhino can find it by name.
3. Create a toolbar button or alias whose macro is:
   - Python: `! _-RunPythonScript "MyScript.py"`
   - RhinoScript: `! _-LoadScript "MyScript.rvb" _-RunScript MySubName`
4. The leading `!` cancels any running command; `-` runs the command in script (no-dialog) mode.

#### Run code at Rhino startup

1. Place a `.rvb`/`.py` in a search path.
2. `Tools → Options → RhinoScript` (or `Python`) → add to **Startup** list. The file executes once per session.

### Gotchas

- **`rhinoscriptsyntax` returns GUIDs, RhinoCommon returns objects.** Mixing them is fine, but `doc.Objects.Find(guid)` is the bridge from a `rs.*` id to a `RhinoObject`.
- **Coordinates differ by surface.** Python uses `(x, y, z)` tuples *or* `Rhino.Geometry.Point3d`; VBScript uses 3-element `Array(x, y, z)`. Never pass a Python list to a VBScript helper through COM.
- **`Option Explicit` is off by default in VBScript.** Typos silently create new variables. Always add `Option Explicit` at the top of `.rvb` files.
- **VBScript has no block scope.** All `Dim`s inside a `Sub` are hoisted to the top of the procedure. Loop counters leak.
- **`Nothing`, `Empty`, and `Null` are different** in VBScript. Use `IsNull` for `Rhino.GetObject` failure, `IsEmpty` for uninitialized `Variant`, `Is Nothing` for object refs.
- **Parentheses change calling semantics in VBScript.** `Call Foo(a, b)` and `Foo a, b` are valid; `Foo(a, b)` (no `Call`, with parens) is **not** a call to a Sub — it’s a syntax error for multi-arg subs and a forced `ByVal` for single-arg.
- **Tolerance is per-document.** Always read `doc.ModelAbsoluteTolerance` rather than hardcoding `0.001`; users work in mm, m, inches, etc.
- **Long loops should poll `Rhino.RhinoApp.EscapeKeyPressed`** so the user can cancel. Otherwise Rhino appears frozen.
- **GUID strings vs `System.Guid`.** `rhinoscriptsyntax` accepts either; RhinoCommon wants `System.Guid`. Convert with `System.Guid(str_id)` if needed.
- **Don’t call `doc.Views.Redraw()` inside a tight loop.** Toggle redraw once outside the loop.
- **`.rvb` is just `.vbs` renamed** with a Rhino-specific extension so Rhino’s `LoadScript` recognizes it. Same VBScript engine.
- **`Rhino.RhinoApp.IsHeadless` may not exist on older Rhino 8 builds.** Use `getattr(Rhino.RhinoApp, "IsHeadless", None)` and check for `None` before using. Fall back to a heuristic (e.g. `sc.doc.Views.Count == 0`) or assume GUI present.
- **`RhinoMath` is at `Rhino.RhinoMath`, not `Rhino.DocObjects.RhinoMath`.** Accessing `Rhino.DocObjects.RhinoMath` raises `AttributeError`.
- **`doc.Objects.AddBrep()` returns `System.Guid.Empty` on failure.** In Rhino 8 CPython the `System` namespace may not be directly importable; check the return value as a string: `str(obj_id) == "00000000-0000-0000-0000-000000000000"`.
- **`rhinoscriptsyntax` has no type stubs.** Static analysers (Pylance/Pyright) flag `import rhinoscriptsyntax as rs` as unresolvable. Suppress with `# type: ignore` on the import line; the module is always available at Rhino runtime.
- **Never name a script after a Python standard-library module** (e.g. `random.py`, `math.py`, `os.py`). IronPython 2.7 (`_-RunPythonScript`) resolves the script directory before stdlib, so any `import random` inside the stdlib (e.g. `tempfile` imports `random` internally) will find your file instead and fail with `Cannot import name <X>`. CPython 3 (`rhinocode`) is unaffected because it resolves stdlib first. Rename the script or avoid importing modules that pull in the shadowed name.
- **Em dashes and other non-ASCII characters silently break `_-RunPythonScript` (IronPython 2.7).** `rhinocode script` uses CPython 3 (UTF-8 by default) so the same file works there, making the failure non-obvious. IronPython 2.7 raises `SyntaxError: Non-ASCII character '\xe2'` at the first offending byte. The most common culprit is the **em dash** (the long dash, U+2014), which many editors type in place of two hyphens. Add `# -*- coding: utf-8 -*-` as line 1 of every script that must run under both runtimes, and replace typographic characters with ASCII equivalents: em dash (U+2014) becomes two hyphens `--`, arrow becomes `->`, multiplication sign becomes `x`.

### Troubleshooting

| Symptom | Fix |
|---|---|
| `rs.GetObject` returns `None` immediately | The user pressed Escape, or your `filter` excludes everything. Re-check `rs.filter.*` flags. |
| “Unable to find script” when running by name | The folder isn’t in `Options → Files → Search paths`. |
| VBScript `Type mismatch` on coordinates | You passed a 2-element array. Rhino requires 3-element `Array(x, y, z)`. |
| Python `ImportError: No module named Rhino` | You’re running CPython outside Rhino. RhinoCommon is only available in Rhino’s embedded Python (or via `rhino3dm` for read-only file work). |
| Created geometry doesn’t appear | You forgot `doc.Views.Redraw()`, or `rs.EnableRedraw(False)` was never re-enabled. |
| Undo undoes only the last object of a batch | Wrap the batch in `BeginUndoRecord` / `EndUndoRecord`. |
| Script works alone but fails as a startup script | Startup runs before any document is open — return early or skip document-dependent work when `sc.doc is None`. |
| `rs.Command("...")` returns `False` | The macro string is malformed. Prefix with `!` and `-`, end every prompt with `_Enter` or a value. |
| `AttributeError: type object 'RhinoApp' has no attribute 'IsHeadless'` | Property added in a later Rhino 8 build. Use `getattr(Rhino.RhinoApp, "IsHeadless", None)` and guard against `None`. |
| `rhinocode script` ignores arguments after the script path | rhinocode concatenates extra tokens onto the file URI. Pass data via a temp file or a Rhino dialog instead. See `references/macros-and-loading.md`. |
| `Cannot import name <X>` inside stdlib (e.g. `tempfile`, `os`) when using `_-RunPythonScript` | Script filename shadows a stdlib module (e.g. `random.py` shadows `random`). IronPython 2.7 searches the script directory before stdlib. Rename the script, or remove the `import` that pulls in the shadowed module and replace it with a direct alternative (e.g. read `%TEMP%` via `os.environ` instead of `import tempfile`). |
| `SyntaxError: Non-ASCII character '\xe2' ... but no encoding declared` | IronPython 2.7 (`_-RunPythonScript`) hit an em dash or similar character. Add `# -*- coding: utf-8 -*-` as line 1, or replace the character: em dash (U+2014) becomes two hyphens `--`, arrow becomes `->`. The same file runs fine under `rhinocode` (CPython 3), which hides the problem. |

<!-- next: written by tools/add-needs.mjs for AI Blocks; not part of the original skill. Edit that tool, not this section. -->
### After this
When you've handed it back, suggest what they could do next in one short question. It's a suggestion: if they're done, that's fine. Offer these, and say they can type their own:
- The [Small usability test planner](https://useaiblocks.com/skills/small-usability-test-planner).
If they pick one and that skill is added here, carry straight on with what you just made. If not, give the link and say what to bring.
<!-- /next -->

### References

- [references/rhinoscriptsyntax-cheatsheet.md](references/rhinoscriptsyntax-cheatsheet.md) — most-used `rs.*` functions by category.
- [references/rhinocommon-map.md](references/rhinocommon-map.md) — which namespace to import for which task.
- [references/macros-and-loading.md](references/macros-and-loading.md) — command-line macro syntax, `LoadScript` / `RunScript`, search paths.
- [references/vbscript-quirks.md](references/vbscript-quirks.md) — VBScript-only traps relevant to RhinoScript.

#### Upstream docs

- RhinoScript landing: <https://docs.mcneel.com/rhino/8/help/en-us/information/rhinoscripting.htm>
- Developer hub: <https://developer.rhino3d.com/>
- RhinoCommon API index: <https://mcneel.github.io/rhinocommon-api-docs/api/RhinoCommon/html/R_Project_RhinoCommon.htm>
- Example scripts repo: <https://github.com/mcneel/rhino-developer-samples/tree/8/rhinoscript>
