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-scripts3description: 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<!-- Generated from harness/github-copilot/skills/rhino3d-scripts/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->78# Rhino 3D scripting910Create 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.1112## When to invoke1314- "Write a RhinoPython script for this modeling task."15- "Debug this .rvb or .vbs RhinoScript."16- "Create a Rhino command macro or toolbar alias."17- "Use RhinoCommon to manipulate geometry and layers."18- "Load this script in the Rhino 8 Script Editor."1920## Prerequisites and context2122- Rhino 7 or later; Rhino 8 is preferred because `_ScriptEditor` supports Python 3, VB, and C#.23- Older editors are `_EditPythonScript` and `_EditScript`.24- Run saved Python with `_-RunPythonScript`; run RhinoScript with `_-LoadScript` plus `_-RunScript`.2526## Choose the scripting surface2728| Surface | Choose when | Extension |29| --- | --- | --- |30| RhinoPython (`rhinoscriptsyntax` plus RhinoCommon) | Default for new scripts; readable and full API access. | `.py` |31| RhinoScript (VBScript) | Maintaining legacy automation or COM/VBA integration. | `.rvb`, `.vbs` |32| RhinoCommon C#/.NET in Script Editor | Performance-critical loops, complex geometry, or .NET libraries. | `.cs` |33| Command macro | Pure command sequence with no variables, loops, or conditionals. | toolbar/alias |3435A macro is not a script. Use a script as soon as the task needs a variable, loop, or conditional.3637## Core patterns3839Python minimal scaffold:4041```python42import rhinoscriptsyntax as rs43import scriptcontext as sc44import Rhino4546def main():47 obj_id = rs.GetObject("Select a curve", filter=rs.filter.curve, preselect=True)48 if not obj_id:49 return50 length = rs.CurveLength(obj_id)51 print("Length: {0:.4f}".format(length))5253if __name__ == "__main__":54 main()55```5657RhinoCommon direct document code:5859```python60import Rhino61import scriptcontext as sc6263doc = sc.doc64_tol = doc.ModelAbsoluteTolerance65circle = Rhino.Geometry.Circle(Rhino.Geometry.Point3d(0, 0, 0), 5.0)66curve_id = doc.Objects.AddCircle(circle)67doc.Views.Redraw()68```6970VBScript scaffold:7172```vbscript73Option Explicit7475Call Main()7677Sub Main()78 Dim strObject79 strObject = Rhino.GetObject("Select a curve", 4)80 If IsNull(strObject) Then Exit Sub81 Rhino.Print "Length: " & Rhino.CurveLength(strObject)82End Sub83```8485Custom RhinoCommon picker:8687```python88go = Rhino.Input.Custom.GetObject()89go.SetCommandPrompt("Select breps")90go.GeometryFilter = Rhino.DocObjects.ObjectType.Brep91go.SubObjectSelect = False92go.GetMultiple(1, 0)93if go.CommandResult() != Rhino.Commands.Result.Success:94 pass95else:96 ids = [go.Object(i).ObjectId for i in range(go.ObjectCount)]97```9899## Workflows100101### Bulk-modify many objects fast1021031. Disable redraw with `rs.EnableRedraw(False)`.1042. Start one undo record with `undo = doc.BeginUndoRecord("My Op")`.1053. Use RhinoCommon directly inside loops instead of high-overhead `rhinoscriptsyntax` calls.1064. Re-enable redraw and call `doc.Views.Redraw()` in `try`/`finally`.1075. Close undo with `doc.EndUndoRecord(undo)`.108109### Distribute a script1101111. Save the `.py` or `.rvb` on disk.1122. Add its folder to `Options -> Files -> Search paths`.1133. Create a toolbar button or alias:114 - Python: `! _-RunPythonScript "MyScript.py"`115 - RhinoScript: `! _-LoadScript "MyScript.rvb" _-RunScript MySubName`1164. Use leading `!` to cancel the running command and `-` for no-dialog script mode.117118### Run code at startup1191201. Place `.rvb` or `.py` in a search path.1212. Add it under `Tools -> Options -> RhinoScript` or `Python` startup list.1223. Return early when `sc.doc is None` because startup can run before a document opens.123124## Gotchas125126- **GUIDs vs objects**: `rhinoscriptsyntax` returns GUIDs; RhinoCommon returns objects. Use `doc.Objects.Find(guid)` to bridge.127- **Coordinates differ**: Python accepts `(x, y, z)` tuples or `Rhino.Geometry.Point3d`; VBScript uses `Array(x, y, z)`.128- **VBScript needs `Option Explicit`**: otherwise typos create variables silently.129- **VBScript has no block scope**: loop counters leak within a `Sub`.130- **`Nothing`, `Empty`, and `Null` differ**: use `IsNull`, `IsEmpty`, or `Is Nothing` correctly.131- **Parentheses alter VBScript calls**: use `Call Foo(a, b)` or `Foo a, b`, not `Foo(a, b)` for multi-argument subs.132- **Tolerance is per document**: read `doc.ModelAbsoluteTolerance`; do not hardcode `0.001`.133- **Long loops should poll `Rhino.RhinoApp.EscapeKeyPressed`** so users can cancel.134- **Convert GUID strings when needed**: RhinoCommon may require `System.Guid`; check `System.Guid.Empty` by string when `System` is unavailable.135- **Do not redraw in tight loops**: toggle once outside the loop.136- **`.rvb` is `.vbs` renamed** for Rhino `LoadScript` recognition.137- **`Rhino.RhinoApp.IsHeadless` may be absent**: use `getattr(Rhino.RhinoApp, "IsHeadless", None)`.138- **`RhinoMath` lives at `Rhino.RhinoMath`**, not `Rhino.DocObjects.RhinoMath`.139- **`doc.Objects.AddBrep()` returns `00000000-0000-0000-0000-000000000000` on failure**.140- **`rhinoscriptsyntax` has no type stubs**: use `# type: ignore` on `import rhinoscriptsyntax as rs` if static analysis complains.141- **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.142- **IronPython 2.7 dislikes non-ASCII without encoding**: add `# -*- coding: utf-8 -*-` and replace typographic em dash/arrow characters with ASCII equivalents for `_-RunPythonScript`.143144## Troubleshooting145146| Symptom | Resolution |147| --- | --- |148| `rs.GetObject` returns `None` immediately | User pressed Escape or the `rs.filter.*` excludes all valid objects. |149| Unable to find script | Add the folder to `Options -> Files -> Search paths`. |150| VBScript `Type mismatch` on coordinates | Pass a 3-element `Array(x, y, z)`. |151| `ImportError: No module named Rhino` | Run inside Rhino; external CPython needs `rhino3dm` only for read-only file work. |152| Geometry does not appear | Call `doc.Views.Redraw()` and re-enable `rs.EnableRedraw(True)`. |153| Undo covers only the last object | Use `BeginUndoRecord` and `EndUndoRecord`. |154| Startup script fails | Guard document-dependent work when `sc.doc is None`. |155| `rs.Command("...")` returns `False` | Prefix macro with `!` and `-`, and end prompts with `_Enter` or a value. |156| `AttributeError: type object 'RhinoApp' has no attribute 'IsHeadless'` | Guard with `getattr(Rhino.RhinoApp, "IsHeadless", None)`. |157| `rhinocode script` ignores arguments | Pass data via a project file or Rhino dialog; see `references/macros-and-loading.md`. |158| `Cannot import name <X>` inside stdlib | Rename scripts that shadow stdlib modules or avoid imports that pull the shadowed name. |159| `SyntaxError: Non-ASCII character 'â'` | Add `# -*- coding: utf-8 -*-` or replace non-ASCII characters. |160161## Progressive disclosure and bundled resources162163- `references/rhinoscriptsyntax-cheatsheet.md`: most-used `rs.*` functions.164- `references/rhinocommon-map.md`: namespace map for RhinoCommon tasks.165- `references/macros-and-loading.md`: command-line macro syntax, `LoadScript`, `RunScript`, and search paths.166- `references/vbscript-quirks.md`: RhinoScript/VBScript traps.167168## Compatibility vocabulary169170Preserve these legacy terms, API names, command placeholders, and literal phrases when applying or migrating this skill:171172- ` and guard against `173- ` as line 1, or replace the character: em dash `174- ` cancels any running command; `175- ` | IronPython 2.7 (`176- ` | Property added in a later Rhino 8 build. Use `177- `! _-Line 0,0,0 10,0,0 _Enter`178- `%TEMP%`179- `) hit an em dash or similar character. Add `180- `, arrow `181- `, end every prompt with `182- `. The same file runs fine under `183- `AttributeError`184- `ByVal`185- `Call`186- `Options → Files → Search paths`187- `Pylance/Pyright`188- `Rhino.*`189- `Rhino.Display`190- `Rhino.DocObjects`191- `Rhino.FileIO`192- `Rhino.Geometry`193- `Rhino.GetObject`194- `Rhino.Input`195- `Rhino.UI`196- `RhinoCommon`197- `RhinoDoc`198- `RhinoObject`199- `SyntaxError: Non-ASCII character '\xe2'`200- `System.Guid(str_id)`201- `TEMP`202- `Tools → Options → RhinoScript`203- `VBA/COM.`204- `Variant`205- `_LoadScript`206- `_RunScript`207- `auto-converted`208- `filter`209- `import`210- `import random`211- `import tempfile`212- `multi-arg`213- `non-obvious`214- `os.environ`215- `per-document`216- `random`217- `re-enabled`218- `sc.doc.Views.Count == 0`219- `scriptcontext`220- `single-arg`221- `standard-library`222- `str(obj_id) == "00000000-0000-0000-0000-000000000000"`223- `str_id`224- `tempfile`225- `ActiveDoc`226- `Rhino.RhinoDoc.ActiveDoc`227- `Views.Count`228229## Output template230231```markdown232## Rhino 3D scripting result233234**Status:** script-created | macro-created | guidance-only | blocked235**Surface:** RhinoPython | RhinoScript | RhinoCommon C# | Command macro236237### Artifact238- `<file or macro>`: <purpose>239240### Runtime notes241| Concern | Decision |242| --- | --- |243| Tolerance | `doc.ModelAbsoluteTolerance` |244| Selection | <rs.GetObject/Rhino.Input.Custom.GetObject/etc.> |245| Undo/redraw | <BeginUndoRecord/EnableRedraw plan> |246| Loading | <RunPythonScript/LoadScript/ScriptEditor> |247248### Validation249- <how to run in Rhino and expected geometry or document effect>250```251252## Quality gate253254- [ ] The selected scripting surface matches the task.255- [ ] Python scripts include `main()` and guard execution with `if __name__ == "__main__":` when appropriate.256- [ ] VBScript includes `Option Explicit`.257- [ ] Document tolerance, redraw, undo, selection, and startup behavior are handled.258- [ ] Long loops allow cancellation with `Rhino.RhinoApp.EscapeKeyPressed` when relevant.259- [ ] Macros use `!`, `-`, `_Enter`, `_RunPythonScript`, `_LoadScript`, or `_RunScript` correctly.260- [ ] Runtime-specific gotchas for IronPython 2.7, CPython 3, and `rhinocode` are considered.261262## References263264- [RhinoScript landing](https://docs.mcneel.com/rhino/8/help/en-us/information/rhinoscripting.htm)265- [Developer hub](https://developer.rhino3d.com/)266- [RhinoCommon API index](https://mcneel.github.io/rhinocommon-api-docs/api/RhinoCommon/html/R_Project_RhinoCommon.htm)267- [Example scripts repo](https://github.com/mcneel/rhino-developer-samples/tree/8/rhinoscript)
Run npx skillmds@latest add paulasilvatech/rhino3d-scripts 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.