Author and debug Rhinoceros 3D RhinoScript, RhinoPython, RhinoCommon, C# Script Editor, and command macro automation. Use when asked to write .rvb, .vbs, or .py Rhino scripts; manipulate geometry, layers, blocks, documents, viewports, undo, redraw, or Rhino 8 Script Editor workflows; or use rhinoscriptsyntax, scriptcontext, and Rhino.* namespaces.
Create production-quality Rhino 8+ scripts and macros by choosing the right scripting surface, handling document tolerance, selection, redraw, undo, loading, and runtime differences between RhinoScript, RhinoPython, RhinoCommon, IronPython, and CPython.
When to invoke
"Write a RhinoPython script for this modeling task."
"Debug this .rvb or .vbs RhinoScript."
"Create a Rhino command macro or toolbar alias."
"Use RhinoCommon to manipulate geometry and layers."
"Load this script in the Rhino 8 Script Editor."
Prerequisites and context
Rhino 7 or later; Rhino 8 is preferred because _ScriptEditor supports Python 3, VB, and C#.
Older editors are _EditPythonScript and _EditScript.
Run saved Python with _-RunPythonScript; run RhinoScript with _-LoadScript plus _-RunScript.
Choose the scripting surface
Surface
Choose when
Extension
RhinoPython (rhinoscriptsyntax plus RhinoCommon)
Default for new scripts; readable and full API access.
.py
RhinoScript (VBScript)
Maintaining legacy automation or COM/VBA integration.
.rvb, .vbs
RhinoCommon C#/.NET in Script Editor
Performance-critical loops, complex geometry, or .NET libraries.
.cs
Command macro
Pure command sequence with no variables, loops, or conditionals.
toolbar/alias
A macro is not a script. Use a script as soon as the task needs a variable, loop, or conditional.
Core patterns
Python minimal scaffold:
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()
Option Explicit
Call Main()
Sub Main()
Dim strObject
strObject = Rhino.GetObject("Select a curve", 4)
If IsNull(strObject) Then Exit Sub
Rhino.Print "Length: " & Rhino.CurveLength(strObject)
End Sub
Custom RhinoCommon picker:
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)]
Workflows
Bulk-modify many objects fast
Disable redraw with rs.EnableRedraw(False).
Start one undo record with undo = doc.BeginUndoRecord("My Op").
Use RhinoCommon directly inside loops instead of high-overhead rhinoscriptsyntax calls.
Re-enable redraw and call doc.Views.Redraw() in try/finally.
Close undo with doc.EndUndoRecord(undo).
Distribute a script
Save the .py or .rvb on disk.
Add its folder to Options -> Files -> Search paths.
VBScript has no block scope: loop counters leak within a Sub.
Nothing, Empty, and Null differ: use IsNull, IsEmpty, or Is Nothing correctly.
Parentheses alter VBScript calls: use Call Foo(a, b) or Foo a, b, not Foo(a, b) for multi-argument subs.
Tolerance is per document: read doc.ModelAbsoluteTolerance; do not hardcode 0.001.
Long loops should poll Rhino.RhinoApp.EscapeKeyPressed so users can cancel.
Convert GUID strings when needed: RhinoCommon may require System.Guid; check System.Guid.Empty by string when System is unavailable.
Do not redraw in tight loops: toggle once outside the loop.
.rvb is .vbs renamed for Rhino LoadScript recognition.
Rhino.RhinoApp.IsHeadless may be absent: use getattr(Rhino.RhinoApp, "IsHeadless", None).
RhinoMath lives at Rhino.RhinoMath, not Rhino.DocObjects.RhinoMath.
doc.Objects.AddBrep() returns 00000000-0000-0000-0000-000000000000 on failure.
rhinoscriptsyntax has no type stubs: use # type: ignore on import rhinoscriptsyntax as rs if static analysis complains.
Do not name scripts after Python stdlib modules such as random.py, math.py, or os.py; IronPython 2.7 resolves the script folder first.
IronPython 2.7 dislikes non-ASCII without encoding: add # -*- coding: utf-8 -*- and replace typographic em dash/arrow characters with ASCII equivalents for _-RunPythonScript.
Troubleshooting
Symptom
Resolution
rs.GetObject returns None immediately
User pressed Escape or the rs.filter.* excludes all valid objects.
Unable to find script
Add the folder to Options -> Files -> Search paths.
VBScript Type mismatch on coordinates
Pass a 3-element Array(x, y, z).
ImportError: No module named Rhino
Run inside Rhino; external CPython needs rhino3dm only for read-only file work.
Geometry does not appear
Call doc.Views.Redraw() and re-enable rs.EnableRedraw(True).
Undo covers only the last object
Use BeginUndoRecord and EndUndoRecord.
Startup script fails
Guard document-dependent work when sc.doc is None.
rs.Command("...") returns False
Prefix macro with ! and -, and end prompts with _Enter or a value.
AttributeError: type object 'RhinoApp' has no attribute 'IsHeadless'
Guard with getattr(Rhino.RhinoApp, "IsHeadless", None).
rhinocode script ignores arguments
Pass data via a project file or Rhino dialog; see references/macros-and-loading.md.
Cannot import name <X> inside stdlib
Rename scripts that shadow stdlib modules or avoid imports that pull the shadowed name.
SyntaxError: Non-ASCII character 'â'
Add # -*- coding: utf-8 -*- or replace non-ASCII characters.
1---2name: rhino3d-scripts-23description: Author and debug Rhinoceros 3D RhinoScript, RhinoPython, RhinoCommon, C# Script Editor, and command macro automation. Use when asked to write .rvb, .vbs, or .py Rhino scripts; manipulate geometry, layers, blocks, documents, viewports, undo, redraw, or Rhino 8 Script Editor workflows; or use rhinoscriptsyntax, scriptcontext, and Rhino.* namespaces.4---56# Rhino 3D scripting78Create production-quality Rhino 8+ scripts and macros by choosing the right scripting surface, handling document tolerance, selection, redraw, undo, loading, and runtime differences between RhinoScript, RhinoPython, RhinoCommon, IronPython, and CPython.910## When to invoke1112- "Write a RhinoPython script for this modeling task."13- "Debug this .rvb or .vbs RhinoScript."14- "Create a Rhino command macro or toolbar alias."15- "Use RhinoCommon to manipulate geometry and layers."16- "Load this script in the Rhino 8 Script Editor."1718## Prerequisites and context1920- Rhino 7 or later; Rhino 8 is preferred because `_ScriptEditor` supports Python 3, VB, and C#.21- Older editors are `_EditPythonScript` and `_EditScript`.22- Run saved Python with `_-RunPythonScript`; run RhinoScript with `_-LoadScript` plus `_-RunScript`.2324## Choose the scripting surface2526| Surface | Choose when | Extension |27| --- | --- | --- |28| RhinoPython (`rhinoscriptsyntax` plus RhinoCommon) | Default for new scripts; readable and full API access. | `.py` |29| RhinoScript (VBScript) | Maintaining legacy automation or COM/VBA integration. | `.rvb`, `.vbs` |30| RhinoCommon C#/.NET in Script Editor | Performance-critical loops, complex geometry, or .NET libraries. | `.cs` |31| Command macro | Pure command sequence with no variables, loops, or conditionals. | toolbar/alias |3233A macro is not a script. Use a script as soon as the task needs a variable, loop, or conditional.3435## Core patterns3637Python minimal scaffold:3839```python40import rhinoscriptsyntax as rs41import scriptcontext as sc42import Rhino4344def main():45 obj_id = rs.GetObject("Select a curve", filter=rs.filter.curve, preselect=True)46 if not obj_id:47 return48 length = rs.CurveLength(obj_id)49 print("Length: {0:.4f}".format(length))5051if __name__ == "__main__":52 main()53```5455RhinoCommon direct document code:5657```python58import Rhino59import scriptcontext as sc6061doc = sc.doc62_tol = doc.ModelAbsoluteTolerance63circle = Rhino.Geometry.Circle(Rhino.Geometry.Point3d(0, 0, 0), 5.0)64curve_id = doc.Objects.AddCircle(circle)65doc.Views.Redraw()66```6768VBScript scaffold:6970```vbscript71Option Explicit7273Call Main()7475Sub Main()76 Dim strObject77 strObject = Rhino.GetObject("Select a curve", 4)78 If IsNull(strObject) Then Exit Sub79 Rhino.Print "Length: " & Rhino.CurveLength(strObject)80End Sub81```8283Custom RhinoCommon picker:8485```python86go = Rhino.Input.Custom.GetObject()87go.SetCommandPrompt("Select breps")88go.GeometryFilter = Rhino.DocObjects.ObjectType.Brep89go.SubObjectSelect = False90go.GetMultiple(1, 0)91if go.CommandResult() != Rhino.Commands.Result.Success:92 pass93else:94 ids = [go.Object(i).ObjectId for i in range(go.ObjectCount)]95```9697## Workflows9899### Bulk-modify many objects fast1001011. Disable redraw with `rs.EnableRedraw(False)`.1022. Start one undo record with `undo = doc.BeginUndoRecord("My Op")`.1033. Use RhinoCommon directly inside loops instead of high-overhead `rhinoscriptsyntax` calls.1044. Re-enable redraw and call `doc.Views.Redraw()` in `try`/`finally`.1055. Close undo with `doc.EndUndoRecord(undo)`.106107### Distribute a script1081091. Save the `.py` or `.rvb` on disk.1102. Add its folder to `Options -> Files -> Search paths`.1113. Create a toolbar button or alias:112 - Python: `! _-RunPythonScript "MyScript.py"`113 - RhinoScript: `! _-LoadScript "MyScript.rvb" _-RunScript MySubName`1144. Use leading `!` to cancel the running command and `-` for no-dialog script mode.115116### Run code at startup1171181. Place `.rvb` or `.py` in a search path.1192. Add it under `Tools -> Options -> RhinoScript` or `Python` startup list.1203. Return early when `sc.doc is None` because startup can run before a document opens.121122## Gotchas123124- **GUIDs vs objects**: `rhinoscriptsyntax` returns GUIDs; RhinoCommon returns objects. Use `doc.Objects.Find(guid)` to bridge.125- **Coordinates differ**: Python accepts `(x, y, z)` tuples or `Rhino.Geometry.Point3d`; VBScript uses `Array(x, y, z)`.126- **VBScript needs `Option Explicit`**: otherwise typos create variables silently.127- **VBScript has no block scope**: loop counters leak within a `Sub`.128- **`Nothing`, `Empty`, and `Null` differ**: use `IsNull`, `IsEmpty`, or `Is Nothing` correctly.129- **Parentheses alter VBScript calls**: use `Call Foo(a, b)` or `Foo a, b`, not `Foo(a, b)` for multi-argument subs.130- **Tolerance is per document**: read `doc.ModelAbsoluteTolerance`; do not hardcode `0.001`.131- **Long loops should poll `Rhino.RhinoApp.EscapeKeyPressed`** so users can cancel.132- **Convert GUID strings when needed**: RhinoCommon may require `System.Guid`; check `System.Guid.Empty` by string when `System` is unavailable.133- **Do not redraw in tight loops**: toggle once outside the loop.134- **`.rvb` is `.vbs` renamed** for Rhino `LoadScript` recognition.135- **`Rhino.RhinoApp.IsHeadless` may be absent**: use `getattr(Rhino.RhinoApp, "IsHeadless", None)`.136- **`RhinoMath` lives at `Rhino.RhinoMath`**, not `Rhino.DocObjects.RhinoMath`.137- **`doc.Objects.AddBrep()` returns `00000000-0000-0000-0000-000000000000` on failure**.138- **`rhinoscriptsyntax` has no type stubs**: use `# type: ignore` on `import rhinoscriptsyntax as rs` if static analysis complains.139- **Do not name scripts after Python stdlib modules** such as `random.py`, `math.py`, or `os.py`; IronPython 2.7 resolves the script folder first.140- **IronPython 2.7 dislikes non-ASCII without encoding**: add `# -*- coding: utf-8 -*-` and replace typographic em dash/arrow characters with ASCII equivalents for `_-RunPythonScript`.141142## Troubleshooting143144| Symptom | Resolution |145| --- | --- |146| `rs.GetObject` returns `None` immediately | User pressed Escape or the `rs.filter.*` excludes all valid objects. |147| Unable to find script | Add the folder to `Options -> Files -> Search paths`. |148| VBScript `Type mismatch` on coordinates | Pass a 3-element `Array(x, y, z)`. |149| `ImportError: No module named Rhino` | Run inside Rhino; external CPython needs `rhino3dm` only for read-only file work. |150| Geometry does not appear | Call `doc.Views.Redraw()` and re-enable `rs.EnableRedraw(True)`. |151| Undo covers only the last object | Use `BeginUndoRecord` and `EndUndoRecord`. |152| Startup script fails | Guard document-dependent work when `sc.doc is None`. |153| `rs.Command("...")` returns `False` | Prefix macro with `!` and `-`, and end prompts with `_Enter` or a value. |154| `AttributeError: type object 'RhinoApp' has no attribute 'IsHeadless'` | Guard with `getattr(Rhino.RhinoApp, "IsHeadless", None)`. |155| `rhinocode script` ignores arguments | Pass data via a project file or Rhino dialog; see `references/macros-and-loading.md`. |156| `Cannot import name <X>` inside stdlib | Rename scripts that shadow stdlib modules or avoid imports that pull the shadowed name. |157| `SyntaxError: Non-ASCII character 'â'` | Add `# -*- coding: utf-8 -*-` or replace non-ASCII characters. |158159## Progressive disclosure and bundled resources160161- `references/rhinoscriptsyntax-cheatsheet.md`: most-used `rs.*` functions.162- `references/rhinocommon-map.md`: namespace map for RhinoCommon tasks.163- `references/macros-and-loading.md`: command-line macro syntax, `LoadScript`, `RunScript`, and search paths.164- `references/vbscript-quirks.md`: RhinoScript/VBScript traps.165166## Compatibility vocabulary167168Preserve these legacy terms, API names, command placeholders, and literal phrases when applying or migrating this skill:169170- ` and guard against `171- ` as line 1, or replace the character: em dash `172- ` cancels any running command; `173- ` | IronPython 2.7 (`174- ` | Property added in a later Rhino 8 build. Use `175- `! _-Line 0,0,0 10,0,0 _Enter`176- `%TEMP%`177- `) hit an em dash or similar character. Add `178- `, arrow `179- `, end every prompt with `180- `. The same file runs fine under `181- `AttributeError`182- `ByVal`183- `Call`184- `Options → Files → Search paths`185- `Pylance/Pyright`186- `Rhino.*`187- `Rhino.Display`188- `Rhino.DocObjects`189- `Rhino.FileIO`190- `Rhino.Geometry`191- `Rhino.GetObject`192- `Rhino.Input`193- `Rhino.UI`194- `RhinoCommon`195- `RhinoDoc`196- `RhinoObject`197- `SyntaxError: Non-ASCII character '\xe2'`198- `System.Guid(str_id)`199- `TEMP`200- `Tools → Options → RhinoScript`201- `VBA/COM.`202- `Variant`203- `_LoadScript`204- `_RunScript`205- `auto-converted`206- `filter`207- `import`208- `import random`209- `import tempfile`210- `multi-arg`211- `non-obvious`212- `os.environ`213- `per-document`214- `random`215- `re-enabled`216- `sc.doc.Views.Count == 0`217- `scriptcontext`218- `single-arg`219- `standard-library`220- `str(obj_id) == "00000000-0000-0000-0000-000000000000"`221- `str_id`222- `tempfile`223- `ActiveDoc`224- `Rhino.RhinoDoc.ActiveDoc`225- `Views.Count`226227## Output template228229```markdown230## Rhino 3D scripting result231232**Status:** script-created | macro-created | guidance-only | blocked233**Surface:** RhinoPython | RhinoScript | RhinoCommon C# | Command macro234235### Artifact236- `<file or macro>`: <purpose>237238### Runtime notes239| Concern | Decision |240| --- | --- |241| Tolerance | `doc.ModelAbsoluteTolerance` |242| Selection | <rs.GetObject/Rhino.Input.Custom.GetObject/etc.> |243| Undo/redraw | <BeginUndoRecord/EnableRedraw plan> |244| Loading | <RunPythonScript/LoadScript/ScriptEditor> |245246### Validation247- <how to run in Rhino and expected geometry or document effect>248```249250## Quality gate251252- [ ] The selected scripting surface matches the task.253- [ ] Python scripts include `main()` and guard execution with `if __name__ == "__main__":` when appropriate.254- [ ] VBScript includes `Option Explicit`.255- [ ] Document tolerance, redraw, undo, selection, and startup behavior are handled.256- [ ] Long loops allow cancellation with `Rhino.RhinoApp.EscapeKeyPressed` when relevant.257- [ ] Macros use `!`, `-`, `_Enter`, `_RunPythonScript`, `_LoadScript`, or `_RunScript` correctly.258- [ ] Runtime-specific gotchas for IronPython 2.7, CPython 3, and `rhinocode` are considered.259260## References261262- [RhinoScript landing](https://docs.mcneel.com/rhino/8/help/en-us/information/rhinoscripting.htm)263- [Developer hub](https://developer.rhino3d.com/)264- [RhinoCommon API index](https://mcneel.github.io/rhinocommon-api-docs/api/RhinoCommon/html/R_Project_RhinoCommon.htm)265- [Example scripts repo](https://github.com/mcneel/rhino-developer-samples/tree/8/rhinoscript)
Run npx skillmds@latest add paulasilvatech/rhino3d-scripts-2 in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Author and debug Rhinoceros 3D RhinoScript, RhinoPython, RhinoCommon, C# Script Editor, and command macro automation. Use when asked to write .rvb, .vbs, or .py Rhino scripts; manipulate geometry, layers, blocks, documents, viewports, undo, redraw, or Rhino 8 Script Editor workflows; or use rhinoscriptsyntax, scriptcontext, and Rhino.* namespaces. It is listed under Coding & Dev Tools on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
paulasilvatech (@paulasilvatech) published this skill. Their other Agent Skills are listed on their SkillMD profile.