This is page 1 of 8. Use http://codebase.md/jingcheng-chen/rhinomcp?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .github
│ └── workflows
│ ├── mcp-server-publish.yml
│ └── rhino-plugin-publish.yml
├── .gitignore
├── assets
│ ├── claude_enable_instruction.jpg
│ ├── cursor_enable_instruction.jpg
│ ├── cursor_usage_instruction.jpg
│ ├── demo1.jpg
│ ├── demo2.jpg
│ ├── rhino_plugin_instruction.jpg
│ └── rhinomcp_logo.svg
├── demo_chats
│ ├── create_6x6x6_boxes.txt
│ └── create_rhinoceros_lego_blocks.txt
├── LICENSE
├── README.md
├── rhino_mcp_plugin
│ ├── .gitignore
│ ├── Commands
│ │ ├── MCPStartCommand.cs
│ │ ├── MCPStopCommand.cs
│ │ └── MCPVersionCommand.cs
│ ├── EmbeddedResources
│ │ └── plugin-utility.ico
│ ├── Functions
│ │ ├── _utils.cs
│ │ ├── CreateLayer.cs
│ │ ├── CreateObject.cs
│ │ ├── CreateObjects.cs
│ │ ├── DeleteLayer.cs
│ │ ├── DeleteObject.cs
│ │ ├── ExecuteRhinoscript.cs
│ │ ├── GetDocumentInfo.cs
│ │ ├── GetObjectInfo.cs
│ │ ├── GetOrSetCurrentLayer.cs
│ │ ├── GetSelectedObjectsInfo.cs
│ │ ├── ModifyObject.cs
│ │ ├── ModifyObjects.cs
│ │ └── SelectObjects.cs
│ ├── manifest.yml
│ ├── Properties
│ │ ├── AssemblyInfo.cs
│ │ └── launchSettings.json
│ ├── rhinomcp.csproj
│ ├── rhinomcp.sln
│ ├── RhinoMCPPlugin.cs
│ ├── RhinoMCPServer.cs
│ ├── RhinoMCPServerController.cs
│ └── Serializers
│ └── Serializer.cs
└── rhino_mcp_server
├── .gitignore
├── dev.sh
├── main.py
├── pyproject.toml
├── README.md
├── src
│ └── rhinomcp
│ ├── __init__.py
│ ├── prompts
│ │ └── assert_general_strategy.py
│ ├── server.py
│ ├── static
│ │ └── rhinoscriptsyntax.py
│ └── tools
│ ├── create_layer.py
│ ├── create_object.py
│ ├── create_objects.py
│ ├── delete_layer.py
│ ├── delete_object.py
│ ├── execute_rhinoscript_python_code.py
│ ├── get_document_info.py
│ ├── get_object_info.py
│ ├── get_or_set_current_layer.py
│ ├── get_rhinoscript_python_code_guide.py
│ ├── get_rhinoscript_python_function_names.py
│ ├── get_selected_objects_info.py
│ ├── modify_object.py
│ ├── modify_objects.py
│ └── select_objects.py
├── static
│ ├── application.py
│ ├── block.py
│ ├── compat.py
│ ├── curve.py
│ ├── dimension.py
│ ├── document.py
│ ├── geometry.py
│ ├── grips.py
│ ├── group.py
│ ├── hatch.py
│ ├── layer.py
│ ├── light.py
│ ├── line.py
│ ├── linetype.py
│ ├── material.py
│ ├── mesh.py
│ ├── object.py
│ ├── plane.py
│ ├── pointvector.py
│ ├── selection.py
│ ├── surface.py
│ ├── toolbar.py
│ ├── transformation.py
│ ├── userdata.py
│ ├── userinterface.py
│ ├── utility.py
│ └── view.py
└── uv.lock
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | .DS_Store
2 | .vscode
3 | .cursor
4 | node_modules/
```
--------------------------------------------------------------------------------
/rhino_mcp_server/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # Virtual environments
7 | venv/
8 | .env/
9 | .venv/
10 | env/
11 | pip-wheel-metadata/
12 | pip-log.txt
13 |
14 | # Package metadata
15 | *.egg-info/
16 | *.dist-info/
17 | .installed.cfg
18 |
19 | # Build artifacts
20 | build/
21 | dist/
22 | wheelhouse/
23 |
24 | # Testing & coverage
25 | .coverage
26 | .coverage.*
27 | htmlcov/
28 | .tox/
29 | .nox/
30 | pytest_cache/
31 | nosetests.xml
32 | coverage.xml
33 | *.cover
34 |
35 | # Documentation
36 | docs/_build/
37 | *.rST~
38 |
39 | # Jupyter Notebook checkpoints
40 | .ipynb_checkpoints/
41 |
42 | # MyPy & type-checking
43 | .mypy_cache/
44 | .dmypy.json
45 | pyre-check/
46 |
47 | # IDE / Editor files
48 | .vscode/
49 | .idea/
50 | *.sublime-workspace
51 | *.sublime-project
52 |
53 | # Logs & Debugging
54 | logs/
55 | *.log
56 | *.out
57 | *.err
58 | debug.log
59 |
60 | # Local environment settings
61 | .env
62 | .envrc
63 | *.local
64 | *.secret
65 |
66 | # Docker
67 | .dockerignore
68 | docker-compose.override.yml
69 |
70 | # Rye, Poetry, and Pipenv lock files
71 | .poetry/
72 | rye.lock
73 | Pipfile
74 | Pipfile.lock
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/.gitignore:
--------------------------------------------------------------------------------
```
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Ll]og/
33 | [Ll]ogs/
34 |
35 | # Visual Studio 2015/2017 cache/options directory
36 | .vs/
37 | # Uncomment if you have tasks that create the project's static files in wwwroot
38 | #wwwroot/
39 |
40 | # Visual Studio 2017 auto generated files
41 | Generated\ Files/
42 |
43 | # MSTest test Results
44 | [Tt]est[Rr]esult*/
45 | [Bb]uild[Ll]og.*
46 |
47 | # NUnit
48 | *.VisualState.xml
49 | TestResult.xml
50 | nunit-*.xml
51 |
52 | # Build Results of an ATL Project
53 | [Dd]ebugPS/
54 | [Rr]eleasePS/
55 | dlldata.c
56 |
57 | # Benchmark Results
58 | BenchmarkDotNet.Artifacts/
59 |
60 | # .NET Core
61 | project.lock.json
62 | project.fragment.lock.json
63 | artifacts/
64 |
65 | # ASP.NET Scaffolding
66 | ScaffoldingReadMe.txt
67 |
68 | # StyleCop
69 | StyleCopReport.xml
70 |
71 | # Files built by Visual Studio
72 | *_i.c
73 | *_p.c
74 | *_h.h
75 | *.ilk
76 | *.meta
77 | *.obj
78 | *.iobj
79 | *.pch
80 | *.pdb
81 | *.ipdb
82 | *.pgc
83 | *.pgd
84 | *.rsp
85 | # but not Directory.Build.rsp, as it configures directory-level build defaults
86 | !Directory.Build.rsp
87 | *.sbr
88 | *.tlb
89 | *.tli
90 | *.tlh
91 | *.tmp
92 | *.tmp_proj
93 | *_wpftmp.csproj
94 | *.log
95 | *.tlog
96 | *.vspscc
97 | *.vssscc
98 | .builds
99 | *.pidb
100 | *.svclog
101 | *.scc
102 |
103 | # Chutzpah Test files
104 | _Chutzpah*
105 |
106 | # Visual C++ cache files
107 | ipch/
108 | *.aps
109 | *.ncb
110 | *.opendb
111 | *.opensdf
112 | *.sdf
113 | *.cachefile
114 | *.VC.db
115 | *.VC.VC.opendb
116 |
117 | # Visual Studio profiler
118 | *.psess
119 | *.vsp
120 | *.vspx
121 | *.sap
122 |
123 | # Visual Studio Trace Files
124 | *.e2e
125 |
126 | # TFS 2012 Local Workspace
127 | $tf/
128 |
129 | # Guidance Automation Toolkit
130 | *.gpState
131 |
132 | # ReSharper is a .NET coding add-in
133 | _ReSharper*/
134 | *.[Rr]e[Ss]harper
135 | *.DotSettings.user
136 |
137 | # TeamCity is a build add-in
138 | _TeamCity*
139 |
140 | # DotCover is a Code Coverage Tool
141 | *.dotCover
142 |
143 | # AxoCover is a Code Coverage Tool
144 | .axoCover/*
145 | !.axoCover/settings.json
146 |
147 | # Coverlet is a free, cross platform Code Coverage Tool
148 | coverage*.json
149 | coverage*.xml
150 | coverage*.info
151 |
152 | # Visual Studio code coverage results
153 | *.coverage
154 | *.coveragexml
155 |
156 | # NCrunch
157 | _NCrunch_*
158 | .*crunch*.local.xml
159 | nCrunchTemp_*
160 |
161 | # MightyMoose
162 | *.mm.*
163 | AutoTest.Net/
164 |
165 | # Web workbench (sass)
166 | .sass-cache/
167 |
168 | # Installshield output folder
169 | [Ee]xpress/
170 |
171 | # DocProject is a documentation generator add-in
172 | DocProject/buildhelp/
173 | DocProject/Help/*.HxT
174 | DocProject/Help/*.HxC
175 | DocProject/Help/*.hhc
176 | DocProject/Help/*.hhk
177 | DocProject/Help/*.hhp
178 | DocProject/Help/Html2
179 | DocProject/Help/html
180 |
181 | # Click-Once directory
182 | publish/
183 |
184 | # Publish Web Output
185 | *.[Pp]ublish.xml
186 | *.azurePubxml
187 | # Note: Comment the next line if you want to checkin your web deploy settings,
188 | # but database connection strings (with potential passwords) will be unencrypted
189 | *.pubxml
190 | *.publishproj
191 |
192 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
193 | # checkin your Azure Web App publish settings, but sensitive information contained
194 | # in these scripts will be unencrypted
195 | PublishScripts/
196 |
197 | # NuGet Packages
198 | *.nupkg
199 | # NuGet Symbol Packages
200 | *.snupkg
201 | # The packages folder can be ignored because of Package Restore
202 | **/[Pp]ackages/*
203 | # except build/, which is used as an MSBuild target.
204 | !**/[Pp]ackages/build/
205 | # Uncomment if necessary however generally it will be regenerated when needed
206 | #!**/[Pp]ackages/repositories.config
207 | # NuGet v3's project.json files produces more ignorable files
208 | *.nuget.props
209 | *.nuget.targets
210 |
211 | # Microsoft Azure Build Output
212 | csx/
213 | *.build.csdef
214 |
215 | # Microsoft Azure Emulator
216 | ecf/
217 | rcf/
218 |
219 | # Windows Store app package directories and files
220 | AppPackages/
221 | BundleArtifacts/
222 | Package.StoreAssociation.xml
223 | _pkginfo.txt
224 | *.appx
225 | *.appxbundle
226 | *.appxupload
227 |
228 | # Visual Studio cache files
229 | # files ending in .cache can be ignored
230 | *.[Cc]ache
231 | # but keep track of directories ending in .cache
232 | !?*.[Cc]ache/
233 |
234 | # Others
235 | ClientBin/
236 | ~$*
237 | *~
238 | *.dbmdl
239 | *.dbproj.schemaview
240 | *.jfm
241 | *.pfx
242 | *.publishsettings
243 | orleans.codegen.cs
244 |
245 | # Including strong name files can present a security risk
246 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
247 | #*.snk
248 |
249 | # Since there are multiple workflows, uncomment next line to ignore bower_components
250 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
251 | #bower_components/
252 |
253 | # RIA/Silverlight projects
254 | Generated_Code/
255 |
256 | # Backup & report files from converting an old project file
257 | # to a newer Visual Studio version. Backup files are not needed,
258 | # because we have git ;-)
259 | _UpgradeReport_Files/
260 | Backup*/
261 | UpgradeLog*.XML
262 | UpgradeLog*.htm
263 | ServiceFabricBackup/
264 | *.rptproj.bak
265 |
266 | # SQL Server files
267 | *.mdf
268 | *.ldf
269 | *.ndf
270 |
271 | # Business Intelligence projects
272 | *.rdl.data
273 | *.bim.layout
274 | *.bim_*.settings
275 | *.rptproj.rsuser
276 | *- [Bb]ackup.rdl
277 | *- [Bb]ackup ([0-9]).rdl
278 | *- [Bb]ackup ([0-9][0-9]).rdl
279 |
280 | # Microsoft Fakes
281 | FakesAssemblies/
282 |
283 | # GhostDoc plugin setting file
284 | *.GhostDoc.xml
285 |
286 | # Node.js Tools for Visual Studio
287 | .ntvs_analysis.dat
288 | node_modules/
289 |
290 | # Visual Studio 6 build log
291 | *.plg
292 |
293 | # Visual Studio 6 workspace options file
294 | *.opt
295 |
296 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
297 | *.vbw
298 |
299 | # Visual Studio 6 auto-generated project file (contains which files were open etc.)
300 | *.vbp
301 |
302 | # Visual Studio 6 workspace and project file (working project files containing files to include in project)
303 | *.dsw
304 | *.dsp
305 |
306 | # Visual Studio 6 technical files
307 | *.ncb
308 | *.aps
309 |
310 | # Visual Studio LightSwitch build output
311 | **/*.HTMLClient/GeneratedArtifacts
312 | **/*.DesktopClient/GeneratedArtifacts
313 | **/*.DesktopClient/ModelManifest.xml
314 | **/*.Server/GeneratedArtifacts
315 | **/*.Server/ModelManifest.xml
316 | _Pvt_Extensions
317 |
318 | # Paket dependency manager
319 | .paket/paket.exe
320 | paket-files/
321 |
322 | # FAKE - F# Make
323 | .fake/
324 |
325 | # CodeRush personal settings
326 | .cr/personal
327 |
328 | # Python Tools for Visual Studio (PTVS)
329 | __pycache__/
330 | *.pyc
331 |
332 | # Cake - Uncomment if you are using it
333 | # tools/**
334 | # !tools/packages.config
335 |
336 | # Tabs Studio
337 | *.tss
338 |
339 | # Telerik's JustMock configuration file
340 | *.jmconfig
341 |
342 | # BizTalk build output
343 | *.btp.cs
344 | *.btm.cs
345 | *.odx.cs
346 | *.xsd.cs
347 |
348 | # OpenCover UI analysis results
349 | OpenCover/
350 |
351 | # Azure Stream Analytics local run output
352 | ASALocalRun/
353 |
354 | # MSBuild Binary and Structured Log
355 | *.binlog
356 |
357 | # NVidia Nsight GPU debugger configuration file
358 | *.nvuser
359 |
360 | # MFractors (Xamarin productivity tool) working folder
361 | .mfractor/
362 |
363 | # Local History for Visual Studio
364 | .localhistory/
365 |
366 | # Visual Studio History (VSHistory) files
367 | .vshistory/
368 |
369 | # BeatPulse healthcheck temp database
370 | healthchecksdb
371 |
372 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
373 | MigrationBackup/
374 |
375 | # Ionide (cross platform F# VS Code tools) working folder
376 | .ionide/
377 |
378 | # Fody - auto-generated XML schema
379 | FodyWeavers.xsd
380 |
381 | # VS Code files for those working on multiple tools
382 | .vscode/*
383 | !.vscode/settings.json
384 | !.vscode/tasks.json
385 | !.vscode/launch.json
386 | !.vscode/extensions.json
387 | *.code-workspace
388 |
389 | # Local History for Visual Studio Code
390 | .history/
391 |
392 | # Windows Installer files from build outputs
393 | *.cab
394 | *.msi
395 | *.msix
396 | *.msm
397 | *.msp
398 |
399 | # JetBrains Rider
400 | *.sln.iml
401 | *.idea
```
--------------------------------------------------------------------------------
/rhino_mcp_server/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # RhinoMCP - Rhino Model Context Protocol Integration
2 |
3 | RhinoMCP connects Rhino to Claude AI through the Model Context Protocol (MCP), allowing Claude to directly interact with and control Rhino. This integration enables prompt assisted 3D modeling in Rhino 3D.
4 |
5 | Please visit Github for complete information:
6 |
7 | [Github](https://github.com/jingcheng-chen/rhinomcp)
8 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # RhinoMCP - Rhino Model Context Protocol Integration
2 |
3 | <img src="assets/rhinomcp_logo.svg" alt="RhinoMCP Logo" width="130">
4 |
5 | RhinoMCP connects Rhino to AI agents through the Model Context Protocol (MCP), allowing AI agents to directly interact with and control Rhino. This integration enables prompt assisted 3D modeling in Rhino 3D.
6 |
7 | ## Features
8 |
9 | - **Two-way communication**: Connect AI agents to Rhino through a socket-based server
10 | - **Object manipulation**: Create, modify, and delete 3D objects in Rhino
11 | - **Document inspection**: Get detailed information about the current Rhino document
12 | - **Script execution**: Execute Rhinos python scripts in Rhino (experimental, may not work every time)
13 | - **Get Script Documentation**: Get the documentation of a specific RhinoScript python function
14 | - **Object selection**: Select objects based on filters, e.g. name, color, category, etc. with "and" or "or" logic
15 | - **Set/Create/Delete Layers**: Get or set the current layer, create new layers, or delete layers
16 |
17 | > [!NOTE]
18 | > So far the tool only supports creating primitive objects for proof of concept. More geometries will be added in the future.
19 | > Supported objects: Point, Line, Polyline, Circle, Arc, Ellipse, Curve, Box, Sphere, Cone, Cylinder, Surface (from points)
20 |
21 | ## Demo
22 |
23 | ### Demo 1
24 |
25 | This demo shows how AI can interact with Rhino in two directions. Click the image below to watch the video.
26 |
27 | [](https://youtu.be/pi6dbqUuhI4)
28 |
29 | ### Demo 2
30 |
31 | This demo shows how to ask AI to create custom scripts and execute them in Rhino. Click the image below to watch the video.
32 |
33 | [](https://youtu.be/NFOF_Pjp3qY)
34 |
35 | ## Tutorial
36 |
37 | Thanks to Nate. He has created a showcase and installation [tutorial](https://www.youtube.com/watch?v=z2IBP81ABRM) for this tool.
38 |
39 | ## Components
40 |
41 | The system consists of two main components:
42 |
43 | 1. **MCP Server (`src/rhino_mcp_server/server.py`)**: A Python server that implements the Model Context Protocol and connects to the Rhino plugin
44 | 2. **Rhino Plugin (`src/rhino_mcp_plugin`)**: A Rhino plugin that creates a socket server within Rhino to receive and execute commands
45 |
46 | ## Installation
47 |
48 | ### Prerequisites
49 |
50 | - Rhino 7 or newer (Works onWindows and Mac); make sure you Rhino is up to date.
51 | - Python 3.10 or newer
52 | - uv package manager
53 |
54 | **⚠️ Only run one instance of the MCP server (either on Cursor or Claude Desktop), not both**
55 |
56 | ### Installing the Rhino Plugin
57 |
58 | 1. Go to Tools > Package Manager
59 | 2. Search for `rhinomcp`
60 | 3. Click `Install`
61 |
62 | #### Install uv
63 |
64 | **If you're on Mac, please install uv as**
65 |
66 | ```bash
67 | brew install uv
68 | ```
69 |
70 | **On Windows**
71 |
72 | ```bash
73 | powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
74 | ```
75 |
76 | **⚠️ Do not proceed before installing UV**
77 |
78 | ### Config file
79 |
80 | ```json
81 | {
82 | "mcpServers": {
83 | "rhino": {
84 | "command": "uvx",
85 | "args": ["rhinomcp"]
86 | }
87 | }
88 | }
89 | ```
90 |
91 | ### Claude for Desktop Integration
92 |
93 | Go to Claude > Settings > Developer > Edit Config > claude_desktop_config.json to include the above config file.
94 |
95 | ### Cursor integration
96 |
97 | Make sure your cursor is up to date.
98 |
99 | Create a folder `.cursor` in your project root.
100 |
101 | Create a file `mcp.json` in the `.cursor` folder and include the above config file:
102 |
103 | Go to Cursor Settings > MCP and check if it's enabled.
104 |
105 | ## Usage
106 |
107 | ### Starting the Connection
108 |
109 | 
110 |
111 | 1. In Rhino, type `mcpstart` in the command line
112 | 2. Make sure the MCP server is running in the rhino terminal
113 |
114 | ### Using with Claude
115 |
116 | Once the config file has been set on Claude, and the plugin is running on Rhino, you will see a hammer icon with tools for the RhinoMCP.
117 |
118 | 
119 |
120 | ### Using with Cursor
121 |
122 | Once the config file has been set on Cursor, and the plugin is running on Rhino, you will see the green indicator in front of the MCP server.
123 |
124 | 
125 |
126 | If not, try refresh the server in Cursor. If any console pops up, please do not close it.
127 |
128 | Once it's ready, use `Ctrl+I` to open the chat box and start chatting with Rhino. Make sure you've selected **Agent** mode.
129 |
130 | 
131 |
132 | ## Technical Details
133 |
134 | ### Communication Protocol
135 |
136 | The system uses a simple JSON-based protocol over TCP sockets:
137 |
138 | - **Commands** are sent as JSON objects with a `type` and optional `params`
139 | - **Responses** are JSON objects with a `status` and `result` or `message`
140 |
141 | ## Limitations & Security Considerations
142 |
143 | - The `get_document_info` only fetches max 30 objects, layers, material etc. to avoid huge dataset that overwhelms Claude.
144 | - Complex operations might need to be broken down into smaller steps
145 |
146 | ## Building the tool and publishing
147 |
148 | ### Building and publishing the server
149 |
150 | ```bash
151 | cd rhino_mcp_server
152 | uv build
153 | uv publish
154 | ```
155 |
156 | ### Building and publishing the plugin
157 |
158 | 1. build the tool in Release mode
159 | 2. copy the "manifest.yml" file to the "bin/Release" folder
160 | 3. run `yak build` in the Release folder
161 | 4. run `yak push rhino_mcp_plugin_xxxx.yak` to publish the plugin
162 |
163 | ## Contributing
164 |
165 | Contributions are welcome! Please feel free to submit a Pull Request.
166 |
167 | ## Disclaimer
168 |
169 | This is a third-party integration and not made by Mcneel. Made by [Jingcheng Chen](https://github.com/jingcheng-chen)
170 |
171 | ## Star History
172 |
173 | [](https://www.star-history.com/#jingcheng-chen/rhinomcp&Date)
174 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/static/compat.py:
--------------------------------------------------------------------------------
```python
1 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/dev.sh:
--------------------------------------------------------------------------------
```bash
1 | #! /bin/bash
2 |
3 | # Dev the server
4 | uv venv
5 | uv run mcp dev main.py:mcp
```
--------------------------------------------------------------------------------
/rhino_mcp_server/main.py:
--------------------------------------------------------------------------------
```python
1 | from rhinomcp.server import main as server_main
2 | from rhinomcp.server import mcp
3 |
4 | def main():
5 | """Entry point for the rhinomcp package"""
6 | server_main()
7 |
8 | if __name__ == "__main__":
9 | main()
```
--------------------------------------------------------------------------------
/demo_chats/create_rhinoceros_lego_blocks.txt:
--------------------------------------------------------------------------------
```
1 | 1. Please create a Rhinoceros animal that is composed of cubic blocks. You could colorize the blocks with some carton colors.
2 |
3 | 2. Please change the head to red color.
4 |
5 | 3. Rotate selected object 90 degrees around the Z axis.
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/manifest.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: rhinomcp
2 | version: 0.1.3.6
3 | authors:
4 | - Jingcheng Chen
5 | description: Rhino integration through the Model Context Protocol
6 | keywords:
7 | - rhinomcp
8 | - mcp
9 | - rhino
10 | - ai
11 | - prompt
12 | - modelcontextprotocol
13 | url: 'https://github.com/jingcheng-chen/rhinomcp'
14 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/GetObjectInfo.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using Newtonsoft.Json.Linq;
3 | using Rhino;
4 | using rhinomcp.Serializers;
5 |
6 | namespace RhinoMCPPlugin.Functions;
7 |
8 | public partial class RhinoMCPFunctions
9 | {
10 | public JObject GetObjectInfo(JObject parameters)
11 | {
12 | var obj = getObjectByIdOrName(parameters);
13 |
14 | var data = Serializer.RhinoObject(obj);
15 | data["attributes"] = Serializer.RhinoObjectAttributes(obj);
16 | return data;
17 | }
18 | }
```
--------------------------------------------------------------------------------
/demo_chats/create_6x6x6_boxes.txt:
--------------------------------------------------------------------------------
```
1 | Please create 6x6x6 boxes. Their position are on a 6x6x6 grid. Grid start from the origin (0,0,0) and expand to x,y,z positive directions. Grid size is 10. Box dimension vary from 1-5, starting with 1 at the origin and gradually ends to 5 at the opposite top most corner. They also have a gradient color from blue (small) to red (large) based on their size. Please try to use rhinoscript python code to create the boxes.
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/get_document_info.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp import get_rhino_connection, mcp, logger
4 |
5 | @mcp.tool()
6 | def get_document_info(ctx: Context) -> str:
7 | """Get detailed information about the current Rhino document"""
8 | try:
9 | rhino = get_rhino_connection()
10 | result = rhino.send_command("get_document_info")
11 |
12 | # Just return the JSON representation of what Rhino sent us
13 | return json.dumps(result, indent=2)
14 | except Exception as e:
15 | logger.error(f"Error getting document info from Rhino: {str(e)}")
16 | return f"Error getting document info: {str(e)}"
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Properties/launchSettings.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "profiles": {
3 | "Rhino 8 - netcore (Windows)": {
4 | "commandName": "Executable",
5 | "executablePath": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe",
6 | "commandLineArgs": "/netcore",
7 | "environmentVariables": {
8 | "RHINO_PACKAGE_DIRS": "$(ProjectDir)$(OutputPath)\\"
9 | },
10 | "nativeDebugging": true
11 | },
12 | "Rhino 8 - netcore (macOS)": {
13 | "commandName": "Executable",
14 | "executablePath": "/Applications/Rhino 8.app/Contents/MacOS/Rhinoceros",
15 | "environmentVariables": {
16 | "RHINO_PACKAGE_DIRS": "${workspaceFolder}/bin/Debug"
17 | },
18 | "nativeDebugging": true
19 | }
20 | }
21 | }
22 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/get_selected_objects_info.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp import get_rhino_connection, mcp, logger
4 |
5 | @mcp.tool()
6 | def get_selected_objects_info(ctx: Context, include_attributes: bool = False) -> str:
7 | """Get detailed information about the currently selected objects in Rhino
8 |
9 | Parameters:
10 | - include_attributes: Whether to include the custom user attributes of the objects in the response
11 | """
12 | try:
13 | rhino = get_rhino_connection()
14 | result = rhino.send_command("get_selected_objects_info", {"include_attributes": include_attributes})
15 | return json.dumps(result, indent=2)
16 | except Exception as e:
17 | logger.error(f"Error getting selected objects from Rhino: {str(e)}")
18 | return f"Error getting selected objects: {str(e)}"
19 |
20 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/pyproject.toml:
--------------------------------------------------------------------------------
```toml
1 | [project]
2 | name = "rhinomcp"
3 | version = "0.1.3.6"
4 | description = "Rhino integration through the Model Context Protocol"
5 | readme = "README.md"
6 | requires-python = ">=3.10"
7 | authors = [
8 | {name = "Jingcheng Chen", email = "[email protected]"}
9 | ]
10 | license = {text = "MIT"}
11 | classifiers = [
12 | "Programming Language :: Python :: 3",
13 | "License :: OSI Approved :: MIT License",
14 | "Operating System :: OS Independent",
15 | ]
16 | dependencies = [
17 | "fastmcp>=2.11.2",
18 | "mcp[cli]>=1.12.4",
19 | ]
20 |
21 | [project.scripts]
22 | rhinomcp = "rhinomcp.server:main"
23 |
24 | [build-system]
25 | requires = ["setuptools>=61.0", "wheel"]
26 | build-backend = "setuptools.build_meta"
27 |
28 | [tool.setuptools]
29 | package-dir = {"" = "src"}
30 |
31 | [project.urls]
32 | "Homepage" = "https://github.com/jingcheng-chen/rhinomcp"
33 | "Bug Tracker" = "https://github.com/jingcheng-chen/rhinomcp/issues"
34 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/GetSelectedObjectsInfo.cs:
--------------------------------------------------------------------------------
```csharp
1 | using Newtonsoft.Json.Linq;
2 | using Rhino;
3 | using rhinomcp.Serializers;
4 |
5 | namespace RhinoMCPPlugin.Functions;
6 |
7 | public partial class RhinoMCPFunctions
8 | {
9 | public JObject GetSelectedObjectsInfo(JObject parameters)
10 | {
11 | var includeAttributes = parameters["include_attributes"]?.ToObject<bool>() ?? false;
12 | var doc = RhinoDoc.ActiveDoc;
13 | var selectedObjs = doc.Objects.GetSelectedObjects(false, false);
14 |
15 | var result = new JArray();
16 | foreach (var obj in selectedObjs)
17 | {
18 | var data = Serializer.RhinoObject(obj);
19 | if (includeAttributes)
20 | {
21 | data["attributes"] = Serializer.RhinoObjectAttributes(obj);
22 | }
23 | result.Add(data);
24 | }
25 |
26 | return new JObject
27 | {
28 | ["selected_objects"] = result
29 | };
30 | }
31 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/RhinoMCPServerController.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using Rhino;
7 |
8 | namespace RhinoMCPPlugin
9 | {
10 | class RhinoMCPServerController
11 | {
12 | private static RhinoMCPServer server;
13 |
14 | public static void StartServer()
15 | {
16 | if (server == null)
17 | {
18 | server = new RhinoMCPServer();
19 | }
20 |
21 | server.Start();
22 | RhinoApp.WriteLine("Server started.");
23 | }
24 |
25 | public static void StopServer()
26 | {
27 | if (server != null)
28 | {
29 | server.Stop();
30 | server = null;
31 | RhinoApp.WriteLine("Server stopped.");
32 | }
33 | }
34 |
35 | public static bool IsServerRunning()
36 | {
37 | return server != null;
38 | }
39 | }
40 | }
41 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/get_rhinoscript_python_code_guide.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | from rhinomcp import get_rhino_connection, mcp, logger, rhinoscriptsyntax_json
3 | from typing import Any, List, Dict
4 |
5 |
6 |
7 | @mcp.tool()
8 | def get_rhinoscript_python_code_guide(ctx: Context, function_name: str) -> Dict[str, Any]:
9 | """
10 | Return the RhinoScriptsyntax Details for a specific function.
11 |
12 | Parameters:
13 | - function_name: The name of the function to get the details for.
14 |
15 | You should get the function names first by using the get_rhinoscript_python_function_names tool.
16 | """
17 | try:
18 | for module in rhinoscriptsyntax_json:
19 | for function in module["functions"]:
20 | if function["Name"] == function_name:
21 | return function
22 |
23 | return {"success": False, "message": "Function not found"}
24 |
25 | except Exception as e:
26 | logger.error(f"Error executing code: {str(e)}")
27 | return {"success": False, "message": str(e)}
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/DeleteObject.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using Newtonsoft.Json.Linq;
3 | using Rhino;
4 | using rhinomcp.Serializers;
5 |
6 | namespace RhinoMCPPlugin.Functions;
7 |
8 | public partial class RhinoMCPFunctions
9 | {
10 | public JObject DeleteObject(JObject parameters)
11 | {
12 | var doc = RhinoDoc.ActiveDoc;
13 | bool all = parameters.ContainsKey("all");
14 |
15 | if (all)
16 | {
17 | doc.Objects.Clear();
18 | doc.Views.Redraw();
19 | return new JObject()
20 | {
21 | ["deleted"] = true,
22 | };
23 | }
24 |
25 |
26 | var obj = getObjectByIdOrName(parameters);
27 |
28 | bool success = doc.Objects.Delete(obj.Id, true);
29 |
30 | if (!success)
31 | throw new InvalidOperationException($"Failed to delete object with ID {obj.Id}");
32 |
33 | // Update views
34 | doc.Views.Redraw();
35 |
36 | return new JObject
37 | {
38 | ["id"] = obj.Id,
39 | ["name"] = obj.Name,
40 | ["deleted"] = true
41 | };
42 | }
43 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Commands/MCPStopCommand.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using Rhino;
4 | using Rhino.Commands;
5 | using Rhino.Geometry;
6 | using Rhino.Input;
7 | using Rhino.Input.Custom;
8 | using System.ComponentModel;
9 | using System.Threading.Tasks;
10 |
11 | namespace RhinoMCPPlugin.Commands
12 | {
13 | public class MCPStopCommand : Command
14 | {
15 | public MCPStopCommand()
16 | {
17 | // Rhino only creates one instance of each command class defined in a
18 | // plug-in, so it is safe to store a refence in a static property.
19 | Instance = this;
20 | }
21 |
22 | ///<summary>The only instance of this command.</summary>
23 | public static MCPStopCommand Instance { get; private set; }
24 |
25 |
26 |
27 | public override string EnglishName => "mcpstop";
28 |
29 | protected override Result RunCommand(RhinoDoc doc, RunMode mode)
30 | {
31 | RhinoMCPServerController.StopServer();
32 | return Result.Success;
33 | }
34 |
35 | }
36 | }
37 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Commands/MCPStartCommand.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using Rhino;
4 | using Rhino.Commands;
5 | using Rhino.Geometry;
6 | using Rhino.Input;
7 | using Rhino.Input.Custom;
8 | using System.ComponentModel;
9 | using System.Threading.Tasks;
10 |
11 | namespace RhinoMCPPlugin.Commands
12 | {
13 | public class MCPStartCommand : Command
14 | {
15 | public MCPStartCommand()
16 | {
17 | // Rhino only creates one instance of each command class defined in a
18 | // plug-in, so it is safe to store a refence in a static property.
19 | Instance = this;
20 | }
21 |
22 | ///<summary>The only instance of this command.</summary>
23 | public static MCPStartCommand Instance { get; private set; }
24 |
25 |
26 |
27 | public override string EnglishName => "mcpstart";
28 |
29 | protected override Result RunCommand(RhinoDoc doc, RunMode mode)
30 | {
31 | RhinoMCPServerController.StartServer();
32 | return Result.Success;
33 | }
34 |
35 | }
36 | }
37 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/delete_object.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 |
7 |
8 | @mcp.tool()
9 | def delete_object(ctx: Context, id: str = None, name: str = None, all: bool = None) -> str:
10 | """
11 | Delete an object from the Rhino document.
12 |
13 | Parameters:
14 | - id: The id of the object to delete
15 | - name: The name of the object to delete
16 | """
17 | try:
18 | # Get the global connection
19 | rhino = get_rhino_connection()
20 |
21 | commandParams = {}
22 | if id is not None:
23 | commandParams["id"] = id
24 | if name is not None:
25 | commandParams["name"] = name
26 | if all:
27 | commandParams["all"] = all
28 |
29 | result = rhino.send_command("delete_object", commandParams)
30 |
31 | return f"Deleted object: {result['name']}"
32 | except Exception as e:
33 | logger.error(f"Error deleting object: {str(e)}")
34 | return f"Error deleting object: {str(e)}"
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/GetOrSetCurrentLayer.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using Newtonsoft.Json.Linq;
5 | using Rhino;
6 | using Rhino.DocObjects;
7 | using Rhino.Geometry;
8 | using rhinomcp.Serializers;
9 |
10 | namespace RhinoMCPPlugin.Functions;
11 |
12 | public partial class RhinoMCPFunctions
13 | {
14 | public JObject GetOrSetCurrentLayer(JObject parameters)
15 | {
16 | // parse meta data
17 | bool hasName = parameters.ContainsKey("name");
18 | bool hasGuid = parameters.ContainsKey("guid");
19 |
20 | string name = hasName ? castToString(parameters.SelectToken("name")) : null;
21 | string guid = hasGuid ? castToString(parameters.SelectToken("guid")) : null;
22 |
23 | var doc = RhinoDoc.ActiveDoc;
24 |
25 | Layer layer = null;
26 | if (hasName) layer = doc.Layers.FindName(name);
27 | if (hasGuid) layer = doc.Layers.FindId(Guid.Parse(guid));
28 |
29 | if (layer != null) doc.Layers.SetCurrentLayerIndex(layer.Index, true);
30 | else layer = doc.Layers.CurrentLayer;
31 |
32 | // Update views
33 | doc.Views.Redraw();
34 |
35 | return Serializer.SerializeLayer(layer);
36 | }
37 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/RhinoMCPPlugin.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using Rhino;
3 |
4 | namespace RhinoMCPPlugin
5 | {
6 | ///<summary>
7 | /// <para>Every RhinoCommon .rhp assembly must have one and only one PlugIn-derived
8 | /// class. DO NOT create instances of this class yourself. It is the
9 | /// responsibility of Rhino to create an instance of this class.</para>
10 | /// <para>To complete plug-in information, please also see all PlugInDescription
11 | /// attributes in AssemblyInfo.cs (you might need to click "Project" ->
12 | /// "Show All Files" to see it in the "Solution Explorer" window).</para>
13 | ///</summary>
14 | public class RhinoMCPPlugin : Rhino.PlugIns.PlugIn
15 | {
16 | public RhinoMCPPlugin()
17 | {
18 | Instance = this;
19 | }
20 |
21 | ///<summary>Gets the only instance of the RhinoMCPPlugin plug-in.</summary>
22 | public static RhinoMCPPlugin Instance { get; private set; }
23 |
24 | // You can override methods here to change the plug-in behavior on
25 | // loading and shut down, add options pages to the Rhino _Option command
26 | // and maintain plug-in wide options in a document.
27 | }
28 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 | using Rhino.PlugIns;
5 |
6 | // Plug-in Description Attributes - all of these are optional.
7 | // These will show in Rhino's option dialog, in the tab Plug-ins.
8 | [assembly: PlugInDescription(DescriptionType.Address, "")]
9 | [assembly: PlugInDescription(DescriptionType.Country, "")]
10 | [assembly: PlugInDescription(DescriptionType.Email, "")]
11 | [assembly: PlugInDescription(DescriptionType.Phone, "")]
12 | [assembly: PlugInDescription(DescriptionType.Fax, "")]
13 | [assembly: PlugInDescription(DescriptionType.Organization, "")]
14 | [assembly: PlugInDescription(DescriptionType.UpdateUrl, "")]
15 | [assembly: PlugInDescription(DescriptionType.WebSite, "")]
16 |
17 | // Icons should be Windows .ico files and contain 32-bit images in the following sizes: 16, 24, 32, 48, and 256.
18 | [assembly: PlugInDescription(DescriptionType.Icon, "rhinomcp.EmbeddedResources.plugin-utility.ico")]
19 |
20 | // The following GUID is for the ID of the typelib if this project is exposed to COM
21 | // This will also be the Guid of the Rhino plug-in
22 | [assembly: Guid("f0b5b632-cc3c-43e7-bc88-da29c47b98bc")]
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Commands/MCPVersionCommand.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using Rhino;
4 | using Rhino.Commands;
5 | using Rhino.Geometry;
6 | using Rhino.Input;
7 | using Rhino.Input.Custom;
8 | using System.ComponentModel;
9 | using System.Threading.Tasks;
10 |
11 | namespace RhinoMCPPlugin.Commands
12 | {
13 | public class MCPVersionCommand : Command
14 | {
15 | public MCPVersionCommand()
16 | {
17 | // Rhino only creates one instance of each command class defined in a
18 | // plug-in, so it is safe to store a refence in a static property.
19 | Instance = this;
20 | }
21 |
22 | ///<summary>The only instance of this command.</summary>
23 | public static MCPVersionCommand Instance { get; private set; }
24 |
25 |
26 |
27 | public override string EnglishName => "mcpversion";
28 |
29 | protected override Result RunCommand(RhinoDoc doc, RunMode mode)
30 | {
31 | // get the version of the plugin from the properties of the project file
32 | var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
33 | Rhino.RhinoApp.WriteLine($"RhinoMCPPlugin version {version}");
34 | return Result.Success;
35 | }
36 |
37 | }
38 | }
39 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/__init__.py:
--------------------------------------------------------------------------------
```python
1 | """Rhino integration through the Model Context Protocol."""
2 |
3 | __version__ = "0.1.0"
4 |
5 | # Expose key classes and functions for easier imports
6 | from .static.rhinoscriptsyntax import rhinoscriptsyntax_json
7 | from .server import RhinoConnection, get_rhino_connection, mcp, logger
8 |
9 | from .prompts.assert_general_strategy import asset_general_strategy
10 |
11 | from .tools.create_object import create_object
12 | from .tools.create_objects import create_objects
13 | from .tools.delete_object import delete_object
14 | from .tools.get_document_info import get_document_info
15 | from .tools.get_object_info import get_object_info
16 | from .tools.get_selected_objects_info import get_selected_objects_info
17 | from .tools.modify_object import modify_object
18 | from .tools.modify_objects import modify_objects
19 | from .tools.execute_rhinoscript_python_code import execute_rhinoscript_python_code
20 | from .tools.get_rhinoscript_python_function_names import get_rhinoscript_python_function_names
21 | from .tools.get_rhinoscript_python_code_guide import get_rhinoscript_python_code_guide
22 | from .tools.select_objects import select_objects
23 | from .tools.create_layer import create_layer
24 | from .tools.get_or_set_current_layer import get_or_set_current_layer
25 | from .tools.delete_layer import delete_layer
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/prompts/assert_general_strategy.py:
--------------------------------------------------------------------------------
```python
1 | from rhinomcp.server import mcp
2 |
3 |
4 | @mcp.prompt()
5 | def asset_general_strategy() -> str:
6 | """Defines the preferred strategy for creating assets in Rhino"""
7 | return """
8 |
9 | QUERY STRATEGY:
10 | - if the id of the object is known, use the id to query the object.
11 | - if the id is not known, use the name of the object to query the object.
12 |
13 |
14 | CREATION STRATEGY:
15 |
16 | 0. Before anything, always check the document from get_document_info().
17 | 1. If the execute_rhinoscript_python_code() function is not able to create the objects, use the create_objects() function.
18 | 2. If there are multiple objects, use the method create_objects() to create multiple objects at once. Do not attempt to create them one by one if they are more than 10.
19 | 3. When including an object into document, ALWAYS make sure that the name of the object is meanful.
20 | 4. Try to include as many objects as possible accurately and efficiently. If the command is not able to include so many data, try to create the objects in batches.
21 |
22 | When creating rhinoscript python code:
23 | - do not hallucinate, only use the syntax that is supported by rhinoscriptsyntax or Rhino,Geometry.
24 | - double check the code if any of the code is not correct, and fix it.
25 | """
26 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/delete_layer.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 | @mcp.tool()
7 | def delete_layer(
8 | ctx: Context,
9 | guid: str = None,
10 | name: str = None
11 | ) -> str:
12 | """
13 | Delete a layer in the Rhino document.
14 | If name is provided, it will try to delete the layer with the given name.
15 | If guid is provided, it will try to delete the layer with the given guid.
16 | If neither is provided, it will return an error.
17 |
18 | Parameters:
19 | - name: The name of the layer to delete.
20 | - guid: The guid of the layer to delete.
21 |
22 | Returns:
23 | A message indicating the layer was deleted.
24 |
25 | Examples of params:
26 | - name: "Layer 1"
27 | - guid: "00000000-0000-0000-0000-000000000000"
28 | """
29 | try:
30 | # Get the global connection
31 | rhino = get_rhino_connection()
32 |
33 | command_params = {}
34 |
35 | if name is not None:
36 | command_params["name"] = name
37 | if guid is not None:
38 | command_params["guid"] = guid
39 |
40 | # Create the layer
41 | result = rhino.send_command("delete_layer", command_params)
42 |
43 | return result["message"]
44 | except Exception as e:
45 | logger.error(f"Error deleting layer: {str(e)}")
46 | return f"Error deleting layer: {str(e)}"
47 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/get_rhinoscript_python_function_names.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | from rhinomcp import get_rhino_connection, mcp, logger, rhinoscriptsyntax_json
3 | from typing import Any, List, Dict
4 |
5 |
6 | @mcp.tool()
7 | def get_rhinoscript_python_function_names(ctx: Context, categories: List[str]) -> List[str]:
8 | """
9 | Return the RhinoScriptsyntax Function Names for specified categories.
10 |
11 | Parameters:
12 | - categories: A list of categories of the RhinoScriptsyntax to get.
13 |
14 | Returns:
15 | - A list of function names that are available in the specified categories.
16 |
17 | The following categories are available:
18 | - application
19 | - block
20 | - compat
21 | - curve
22 | - dimension
23 | - document
24 | - geometry
25 | - grips
26 | - group
27 | - hatch
28 | - layer
29 | - light
30 | - line
31 | - linetype
32 | - material
33 | - mesh
34 | - object
35 | - plane
36 | - pointvector
37 | - selection
38 | - surface
39 | - toolbar
40 | - transformation
41 | - userdata
42 | - userinterface
43 | - utility
44 | - view
45 | """
46 | try:
47 | function_names: List[str] = []
48 | for i in rhinoscriptsyntax_json:
49 | if i["ModuleName"] in categories:
50 | function_names.extend([func["Name"] for func in i["functions"]])
51 |
52 | # return the related functions
53 | return function_names
54 | except Exception as e:
55 | logger.error(f"Error executing code: {str(e)}")
56 | return []
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/create_layer.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 | @mcp.tool()
7 | def create_layer(
8 | ctx: Context,
9 | name: str = None,
10 | color: List[int]= None,
11 | parent: str = None,
12 | ) -> str:
13 | """
14 | Create a new layer in the Rhino document.
15 |
16 | Parameters:
17 | - name: The name of the new layer. If omitted, Rhino automatically generates the layer name.
18 | - color: Optional [r, g, b] color values (0-255) for the layer
19 | - parent: Optional name of the new layer's parent layer. If omitted, the new layer will not have a parent layer.
20 |
21 | Returns:
22 | A message indicating the created layer name.
23 |
24 | Examples of params:
25 | - name: "Layer 1"
26 | - color: [255, 0, 0]
27 | - parent: "Default"
28 | """
29 | try:
30 | # Get the global connection
31 | rhino = get_rhino_connection()
32 |
33 | command_params = {
34 | "name": name
35 | }
36 |
37 | if color is not None: command_params["color"] = color
38 | if parent is not None: command_params["parent"] = parent
39 |
40 | # Create the layer
41 | result = rhino.send_command("create_layer", command_params)
42 |
43 | return f"Created layer: {result['name']}"
44 | except Exception as e:
45 | logger.error(f"Error creating layer: {str(e)}")
46 | return f"Error creating layer: {str(e)}"
47 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/CreateLayer.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using Newtonsoft.Json.Linq;
5 | using Rhino;
6 | using Rhino.DocObjects;
7 | using Rhino.Geometry;
8 | using rhinomcp.Serializers;
9 |
10 | namespace RhinoMCPPlugin.Functions;
11 |
12 | public partial class RhinoMCPFunctions
13 | {
14 | public JObject CreateLayer(JObject parameters)
15 | {
16 | // parse meta data
17 | bool hasName = parameters.ContainsKey("name");
18 | bool hasColor = parameters.ContainsKey("color");
19 | bool hasParent = parameters.ContainsKey("parent");
20 |
21 | string name = hasName ? castToString(parameters.SelectToken("name")) : null;
22 | int[] color = hasColor ? castToIntArray(parameters.SelectToken("color")) : null;
23 | string parent = hasParent ? castToString(parameters.SelectToken("parent")) : null;
24 |
25 | var doc = RhinoDoc.ActiveDoc;
26 |
27 | var layer = new Layer();
28 | if (hasName) layer.Name = name;
29 | if (hasColor) layer.Color = Color.FromArgb(color[0], color[1], color[2]);
30 |
31 | if (hasParent)
32 | {
33 | var parentLayer = doc.Layers.FindName(parent);
34 | if (parentLayer != null)
35 | layer.ParentLayerId = parentLayer.Id;
36 |
37 | }
38 | // Create a box centered at the specified point
39 | var layerId = doc.Layers.Add(layer);
40 | layer = doc.Layers.FindIndex(layerId);
41 |
42 | // Update views
43 | doc.Views.Redraw();
44 |
45 | return Serializer.SerializeLayer(layer);
46 | }
47 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/CreateObjects.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Drawing;
3 | using Newtonsoft.Json.Linq;
4 | using Rhino;
5 | using Rhino.DocObjects;
6 | using Rhino.Geometry;
7 | using rhinomcp.Serializers;
8 |
9 | namespace RhinoMCPPlugin.Functions;
10 |
11 | public partial class RhinoMCPFunctions
12 | {
13 | public JObject CreateObjects(JObject parameters)
14 | {
15 | var doc = RhinoDoc.ActiveDoc;
16 | var results = new JObject();
17 |
18 | // Process each object in the parameters
19 | foreach (var property in parameters.Properties())
20 | {
21 | try
22 | {
23 | // Get the object parameters
24 | JObject objectParams = (JObject)property.Value;
25 |
26 | // Create the object using the existing CreateObject method
27 | JObject result = CreateObject(objectParams);
28 |
29 | // Add the result to our results collection
30 | results[property.Name] = result;
31 | }
32 | catch (Exception ex)
33 | {
34 | // If there's an error creating this object, add the error to the results
35 | results[property.Name] = new JObject
36 | {
37 | ["error"] = ex.Message
38 | };
39 | }
40 | }
41 |
42 | // Update views
43 | doc.Views.Redraw();
44 |
45 | return results;
46 | }
47 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/ModifyObjects.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Drawing;
3 | using System.Linq;
4 | using Newtonsoft.Json.Linq;
5 | using Rhino;
6 | using Rhino.DocObjects;
7 | using Rhino.Geometry;
8 | using rhinomcp.Serializers;
9 |
10 | namespace RhinoMCPPlugin.Functions;
11 |
12 | public partial class RhinoMCPFunctions
13 | {
14 | public JObject ModifyObjects(JObject parameters)
15 | {
16 | bool all = parameters.ContainsKey("all");
17 | JArray objectParameters = (JArray)parameters["objects"];
18 |
19 | var doc = RhinoDoc.ActiveDoc;
20 | var objects = doc.Objects.ToList();
21 |
22 | if (all && objectParameters.Count == 1)
23 | {
24 | // Get the first modification parameters (excluding the "all" property)
25 | JObject firstModification = (JObject)objectParameters.FirstOrDefault()!;
26 |
27 | // Create new parameters object with all object IDs
28 | foreach (var obj in objects)
29 | {
30 | // Create a new copy of the modification parameters for each object
31 | JObject newModification = new JObject(firstModification) { ["id"] = obj.Id.ToString() };
32 | objectParameters.Add(newModification);
33 | }
34 | }
35 |
36 | var i = 0;
37 | foreach (JObject parameter in objectParameters)
38 | {
39 | if (parameter.ContainsKey("id"))
40 | {
41 | ModifyObject(parameter);
42 | i++;
43 | }
44 | }
45 | doc.Views.Redraw();
46 | return new JObject() { ["modified"] = i };
47 | }
48 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/DeleteLayer.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using Newtonsoft.Json.Linq;
5 | using Rhino;
6 | using Rhino.DocObjects;
7 | using Rhino.Geometry;
8 | using rhinomcp.Serializers;
9 |
10 | namespace RhinoMCPPlugin.Functions;
11 |
12 | public partial class RhinoMCPFunctions
13 | {
14 | public JObject DeleteLayer(JObject parameters)
15 | {
16 | // parse meta data
17 | bool hasName = parameters.ContainsKey("name");
18 | bool hasGuid = parameters.ContainsKey("guid");
19 |
20 | string name = hasName ? castToString(parameters.SelectToken("name")) : null;
21 | string guid = hasGuid ? castToString(parameters.SelectToken("guid")) : null;
22 |
23 | var doc = RhinoDoc.ActiveDoc;
24 |
25 | Layer layer = null;
26 | if (hasName) layer = doc.Layers.FindName(name);
27 | if (hasGuid) layer = doc.Layers.FindId(Guid.Parse(guid));
28 |
29 | if (layer == null)
30 | {
31 | return new JObject
32 | {
33 | ["success"] = false,
34 | ["message"] = "Layer not found"
35 | };
36 | }
37 | if (layer == null)
38 | {
39 | return new JObject
40 | {
41 | ["success"] = false,
42 | ["message"] = "Layer not found"
43 | };
44 | }
45 |
46 | name = layer.Name;
47 | doc.Layers.Delete(layer.Index, true);
48 |
49 | // Update views
50 | doc.Views.Redraw();
51 |
52 | return new JObject
53 | {
54 | ["success"] = true,
55 | ["message"] = $"Layer {name} deleted"
56 | };
57 | }
58 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/get_or_set_current_layer.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 | @mcp.tool()
7 | def get_or_set_current_layer(
8 | ctx: Context,
9 | guid: str = None,
10 | name: str = None
11 | ) -> str:
12 | """
13 | Get or set the current layer in the Rhino document.
14 | If name is provided, it will try to set the current layer to the layer with the given name.
15 | If guid is provided, it will try to set the current layer to the layer with the given guid.
16 | If neither is provided, it will return the current layer.
17 |
18 | Parameters:
19 | - name: The name of the layer to set the current layer to.
20 | - guid: The guid of the layer to set the current layer to.
21 |
22 | Returns:
23 | A message indicating the current layer.
24 |
25 | Examples of params:
26 | - name: "Layer 1"
27 | - guid: "00000000-0000-0000-0000-000000000000"
28 | """
29 | try:
30 | # Get the global connection
31 | rhino = get_rhino_connection()
32 |
33 | command_params = {}
34 |
35 | if name is not None:
36 | command_params["name"] = name
37 | if guid is not None:
38 | command_params["guid"] = guid
39 |
40 | # Create the layer
41 | result = rhino.send_command("get_or_set_current_layer", command_params)
42 |
43 | return f"Current layer: {result['name']}"
44 | except Exception as e:
45 | logger.error(f"Error getting or setting current layer: {str(e)}")
46 | return f"Error getting or setting current layer: {str(e)}"
47 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/modify_objects.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 |
7 | @mcp.tool()
8 | def modify_objects(
9 | ctx: Context,
10 | objects: List[Dict[str, Any]],
11 | all: bool = None
12 | ) -> str:
13 | """
14 | Create multiple objects at once in the Rhino document.
15 |
16 | Parameters:
17 | - objects: A List of objects, each containing the parameters for a single object modification
18 | - all: Optional boolean to modify all objects, if true, only one object is required in the objects dictionary
19 |
20 | Each object can have the following parameters:
21 | - id: The id of the object to modify
22 | - new_color: Optional [r, g, b] color values (0-255) for the object
23 | - translation: Optional [x, y, z] translation vector
24 | - rotation: Optional [x, y, z] rotation in radians
25 | - scale: Optional [x, y, z] scale factors
26 | - visible: Optional boolean to set visibility
27 |
28 | Returns:
29 | A message indicating the modified objects.
30 | """
31 | try:
32 | # Get the global connection
33 | rhino = get_rhino_connection()
34 | command_params = {}
35 | command_params["objects"] = objects
36 | if all:
37 | command_params["all"] = all
38 | result = rhino.send_command("modify_objects", command_params)
39 |
40 |
41 | return f"Modified {result['modified']} objects"
42 | except Exception as e:
43 | logger.error(f"Error modifying objects: {str(e)}")
44 | return f"Error modifying objects: {str(e)}"
45 |
46 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/get_object_info.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp import get_rhino_connection, mcp, logger
4 | from typing import Dict, Any
5 |
6 | @mcp.tool()
7 | def get_object_info(ctx: Context, id: str = None, name: str = None) -> Dict[str, Any]:
8 | """
9 | Get detailed information about a specific object in the Rhino document.
10 | The information contains the object's id, name, type, all custom user attributes and geometry info.
11 | You can either provide the id or the object_name of the object to get information about.
12 | If both are provided, the id will be used.
13 |
14 | Returns:
15 | - A dictionary containing the object's information
16 | - The dictionary will have the following keys:
17 | - "id": The id of the object
18 | - "name": The name of the object
19 | - "type": The type of the object
20 | - "layer": The layer of the object
21 | - "material": The material of the object
22 | - "color": The color of the object
23 | - "bounding_box": The bounding box of the object
24 | - "geometry": The geometry info of the object
25 | - "attributes": A dictionary containing all custom user attributes of the object
26 |
27 | Parameters:
28 | - id: The id of the object to get information about
29 | - name: The name of the object to get information about
30 | """
31 | try:
32 | rhino = get_rhino_connection()
33 | return rhino.send_command("get_object_info", {"id": id, "name": name})
34 |
35 | except Exception as e:
36 | logger.error(f"Error getting object info from Rhino: {str(e)}")
37 | return {
38 | "error": str(e)
39 | }
40 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/execute_rhinoscript_python_code.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 |
7 | @mcp.tool()
8 | def execute_rhinoscript_python_code(ctx: Context, code: str) -> Dict[str, Any]:
9 | """
10 | Execute arbitrary RhinoScript code in Rhino.
11 |
12 | Parameters:
13 | - code: The RhinoScript code to execute
14 |
15 | GUIDE:
16 |
17 | 1. To get any output from the script, you should use the python `print` function.
18 | 2. You can get a list of all possible functions names that can be used by using the get_rhinoscript_python_function_names tool.
19 | 3. You can get the details of a specific function by using the get_rhinoscript_python_code_guide tool.
20 |
21 | Example:
22 | - Your task is: "Create a loft surface between two curves."
23 | - get_rhinoscript_python_function_names(["surface", "curve"])
24 | - This will return the function names that are necessary for creating the code.
25 | - get_rhinoscript_python_code_guide("AddLoftSrf")
26 | - This will return the syntax of the code that are necessary for creating the code.
27 |
28 | Any changes made to the document will be undone if the script returns failure.
29 |
30 | DO NOT HALLUCINATE, ONLY USE THE SYNTAX THAT IS SUPPORTED BY RHINO.GEOMETRY OR RHINOSCRIPT.
31 |
32 | """
33 | try:
34 | # Get the global connection
35 | rhino = get_rhino_connection()
36 |
37 | return rhino.send_command("execute_rhinoscript_python_code", {"code": code})
38 |
39 | except Exception as e:
40 | logger.error(f"Error executing code: {str(e)}")
41 | return {"success": False, "message": str(e)}
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/ExecuteRhinoscript.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Drawing;
3 | using Newtonsoft.Json.Linq;
4 | using Rhino;
5 | using Rhino.DocObjects;
6 | using Rhino.Geometry;
7 | using rhinomcp.Serializers;
8 | using Rhino.Runtime;
9 | using System.Text;
10 |
11 | namespace RhinoMCPPlugin.Functions;
12 |
13 | public partial class RhinoMCPFunctions
14 | {
15 | public JObject ExecuteRhinoscript(JObject parameters)
16 | {
17 | var doc = RhinoDoc.ActiveDoc;
18 | string code = parameters["code"]?.ToString();
19 | if (string.IsNullOrEmpty(code))
20 | {
21 | throw new Exception("Code is required");
22 | }
23 |
24 | // register undo
25 | var undoRecordSerialNumber = doc.BeginUndoRecord("ExecuteRhinoScript");
26 |
27 | JObject result = new JObject();
28 |
29 | try
30 | {
31 | var output = new StringBuilder();
32 | // Create a new Python script instance
33 | PythonScript pythonScript = PythonScript.Create();
34 |
35 | pythonScript.Output += (message) =>
36 | {
37 | output.Append(message);
38 | };
39 |
40 | // Setup the script context with the current document
41 | if (doc != null)
42 | pythonScript.SetupScriptContext(doc);
43 |
44 | // Execute the Python code
45 | pythonScript.ExecuteScript(code);
46 |
47 |
48 | result["success"] = true;
49 | result["result"] = $"Script successfully executed! Print output: {output}";
50 | }
51 | catch (Exception ex)
52 | {
53 |
54 | result["success"] = false;
55 | result["message"] = $"Error executing rhinoscript: {ex}";
56 | }
57 | finally
58 | {
59 | // undo
60 | doc.EndUndoRecord(undoRecordSerialNumber);
61 | }
62 |
63 | // if the script failed, undo the changes
64 | if (!result["success"].ToObject<bool>())
65 | {
66 | doc.Undo();
67 | }
68 |
69 | return result;
70 | }
71 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/modify_object.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 |
7 | @mcp.tool()
8 | def modify_object(
9 | ctx: Context,
10 | id: str = None,
11 | name: str = None,
12 | new_name: str = None,
13 | new_color: List[int] = None,
14 | translation: List[float] = None,
15 | rotation: List[float] = None,
16 | scale: List[float] = None,
17 | visible: bool = None
18 | ) -> str:
19 | """
20 | Modify an existing object in the Rhino document.
21 |
22 | Parameters:
23 | - id: The id of the object to modify
24 | - name: The name of the object to modify
25 | - new_name: Optional new name for the object
26 | - new_color: Optional [r, g, b] color values (0-255) for the object
27 | - translation: Optional [x, y, z] translation vector
28 | - rotation: Optional [x, y, z] rotation in radians
29 | - scale: Optional [x, y, z] scale factors
30 | - visible: Optional boolean to set visibility
31 | """
32 | try:
33 | # Get the global connection
34 | rhino = get_rhino_connection()
35 |
36 | params : Dict[str, Any] = {}
37 |
38 | if id is not None:
39 | params["id"] = id
40 | if name is not None:
41 | params["name"] = name
42 | if new_name is not None:
43 | params["new_name"] = new_name
44 | if new_color is not None:
45 | params["new_color"] = new_color
46 | if translation is not None:
47 | params["translation"] = translation
48 | if rotation is not None:
49 | params["rotation"] = rotation
50 | if scale is not None:
51 | params["scale"] = scale
52 | if visible is not None:
53 | params["visible"] = visible
54 |
55 | result = rhino.send_command("modify_object", params)
56 | return f"Modified object: {result['name']}"
57 | except Exception as e:
58 | logger.error(f"Error modifying object: {str(e)}")
59 | return f"Error modifying object: {str(e)}"
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/select_objects.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 |
7 | @mcp.tool()
8 | def select_objects(
9 | ctx: Context,
10 | filters: Dict[str, List[Any]] = {},
11 | filters_type: str = "and",
12 | ) -> str:
13 | """
14 | Select objects in the Rhino document.
15 |
16 | Parameters:
17 | - filters: A dictionary containing the filters. The filters parameter is necessary, unless it's empty, in which case all objects will be selected.
18 | - filters_type: The type of the filters, it's "and" or "or", default is "and"
19 |
20 | Note:
21 | The filter value is always a list, even if it's a single value. The reason is that a filter can contain multiple values, for example when we query by a attribute that has EITHER value1 OR value2.
22 |
23 | The filters dictionary can contain the following keys:
24 | - name: The name of the object
25 | - color: The color of the object, for example [255, 0, 0]
26 |
27 | Additionaly, rhino allows to have user custom attributes, which can be used to filters the objects.
28 | For example, if the object has a user custom attribute called "category", the filters dictionary can contain:
29 | - category: custom_attribute_value
30 |
31 | Example:
32 | filters = {
33 | "name": ["object_name1", "object_name2"],
34 | "category": ["custom_attribute_value"]
35 | },
36 | filters_type = "or"
37 |
38 |
39 | Returns:
40 | A number indicating the number of objects that have been selected.
41 | """
42 | try:
43 | # Get the global connection
44 | rhino = get_rhino_connection()
45 | command_params = {
46 | "filters": filters,
47 | "filters_type": filters_type
48 | }
49 |
50 | result = rhino.send_command("select_objects", command_params)
51 |
52 | return f"Selected {result['count']} objects"
53 | except Exception as e:
54 | logger.error(f"Error selecting objects: {str(e)}")
55 | return f"Error selecting objects: {str(e)}"
56 |
57 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/GetDocumentInfo.cs:
--------------------------------------------------------------------------------
```csharp
1 | using Newtonsoft.Json.Linq;
2 | using Rhino;
3 | using rhinomcp.Serializers;
4 |
5 | namespace RhinoMCPPlugin.Functions;
6 |
7 | public partial class RhinoMCPFunctions
8 | {
9 | public JObject GetDocumentInfo(JObject parameters)
10 | {
11 | const int LIMIT = 30;
12 |
13 | RhinoApp.WriteLine("Getting document info...");
14 |
15 | var doc = RhinoDoc.ActiveDoc;
16 |
17 | var metaData = new JObject
18 | {
19 | ["name"] = doc.Name,
20 | ["date_created"] = doc.DateCreated,
21 | ["date_modified"] = doc.DateLastEdited,
22 | ["tolerance"] = doc.ModelAbsoluteTolerance,
23 | ["angle_tolerance"] = doc.ModelAngleToleranceDegrees,
24 | ["path"] = doc.Path,
25 | ["units"] = doc.ModelUnitSystem.ToString(),
26 | };
27 |
28 | var objectData = new JArray();
29 |
30 | // Collect minimal object information (limit to first 10 objects)
31 | int count = 0;
32 | foreach (var docObject in doc.Objects)
33 | {
34 | if (count >= LIMIT) break;
35 |
36 | objectData.Add(Serializer.RhinoObject(docObject));
37 | count++;
38 | }
39 |
40 | var layerData = new JArray();
41 |
42 | count = 0;
43 | foreach (var docLayer in doc.Layers)
44 | {
45 | if (count >= LIMIT) break;
46 | layerData.Add(new JObject
47 | {
48 | ["id"] = docLayer.Id.ToString(),
49 | ["name"] = docLayer.Name,
50 | ["color"] = docLayer.Color.ToString(),
51 | ["visible"] = docLayer.IsVisible,
52 | ["locked"] = docLayer.IsLocked
53 | });
54 | count++;
55 | }
56 |
57 |
58 | var result = new JObject
59 | {
60 | ["meta_data"] = metaData,
61 | ["object_count"] = doc.Objects.Count,
62 | ["objects"] = objectData,
63 | ["layer_count"] = doc.Layers.Count,
64 | ["layers"] = layerData
65 | };
66 |
67 | RhinoApp.WriteLine($"Document info collected: {count} objects");
68 | return result;
69 | }
70 | }
```
--------------------------------------------------------------------------------
/.github/workflows/mcp-server-publish.yml:
--------------------------------------------------------------------------------
```yaml
1 | # This workflow will upload a Python Package to PyPI when a release is created
2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
3 |
4 | # This workflow uses actions that are not certified by GitHub.
5 | # They are provided by a third-party and are governed by
6 | # separate terms of service, privacy policy, and support
7 | # documentation.
8 |
9 | name: Upload Python Package
10 |
11 | on:
12 | release:
13 | types: [published]
14 |
15 | permissions:
16 | contents: read
17 |
18 | jobs:
19 | release-build:
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 |
25 | - uses: actions/setup-python@v5
26 | with:
27 | python-version: "3.x"
28 |
29 | - name: Build release distributions
30 | run: |
31 | cd rhino_mcp_server
32 | python -m pip install build
33 | python -m build
34 |
35 | - name: Upload distributions
36 | uses: actions/upload-artifact@v4
37 | with:
38 | name: release-dists
39 | path: rhino_mcp_server/dist/
40 |
41 | pypi-publish:
42 | runs-on: ubuntu-latest
43 | needs:
44 | - release-build
45 | permissions:
46 | # IMPORTANT: this permission is mandatory for trusted publishing
47 | id-token: write
48 |
49 | # Dedicated environments with protections for publishing are strongly recommended.
50 | # For more information, see: https://docs.github.com/en/actions/deployment/targeting-different-environments/using-environments-for-deployment#deployment-protection-rules
51 | environment:
52 | name: pypi
53 | # OPTIONAL: uncomment and update to include your PyPI project URL in the deployment status:
54 | url: https://pypi.org/p/rhinomcp
55 | #
56 | # ALTERNATIVE: if your GitHub Release name is the PyPI project version string
57 | # ALTERNATIVE: exactly, uncomment the following line instead:
58 | # url: https://pypi.org/project/YOURPROJECT/${{ github.event.release.name }}
59 |
60 | steps:
61 | - name: Retrieve release distributions
62 | uses: actions/download-artifact@v4
63 | with:
64 | name: release-dists
65 | path: rhino_mcp_server/dist/
66 |
67 | - name: Publish release distributions to PyPI
68 | uses: pypa/gh-action-pypi-publish@release/v1
69 | with:
70 | password: ${{ secrets.PYPI_API_TOKEN }}
71 | packages-dir: rhino_mcp_server/dist/
72 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/ModifyObject.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Drawing;
3 | using Newtonsoft.Json.Linq;
4 | using Rhino;
5 | using Rhino.DocObjects;
6 | using Rhino.Geometry;
7 | using rhinomcp.Serializers;
8 |
9 | namespace RhinoMCPPlugin.Functions;
10 |
11 | public partial class RhinoMCPFunctions
12 | {
13 | public JObject ModifyObject(JObject parameters)
14 | {
15 | var doc = RhinoDoc.ActiveDoc;
16 | var obj = getObjectByIdOrName(parameters);
17 | var geometry = obj.Geometry;
18 | var xform = Transform.Identity;
19 |
20 | // Handle different modifications based on parameters
21 | bool attributesModified = false;
22 | bool geometryModified = false;
23 |
24 | // Change name if provided
25 | if (parameters["new_name"] != null)
26 | {
27 | string name = parameters["new_name"].ToString();
28 | obj.Attributes.Name = name;
29 | attributesModified = true;
30 | }
31 |
32 | // Change color if provided
33 | if (parameters["new_color"] != null)
34 | {
35 | int[] color = parameters["new_color"]?.ToObject<int[]>() ?? new[] { 0, 0, 0 };
36 | obj.Attributes.ObjectColor = Color.FromArgb(color[0], color[1], color[2]);
37 | obj.Attributes.ColorSource = ObjectColorSource.ColorFromObject;
38 | attributesModified = true;
39 | }
40 |
41 | // Change translation if provided
42 | if (parameters["translation"] != null)
43 | {
44 | xform *= applyTranslation(parameters);
45 | geometryModified = true;
46 | }
47 |
48 | // Apply scale if provided
49 | if (parameters["scale"] != null)
50 | {
51 | xform *= applyScale(parameters, geometry);
52 | geometryModified = true;
53 | }
54 |
55 | // Apply rotation if provided
56 | if (parameters["rotation"] != null)
57 | {
58 | xform *= applyRotation(parameters, geometry);
59 | geometryModified = true;
60 | }
61 |
62 | if (attributesModified)
63 | {
64 | // Update the object attributes if needed
65 | doc.Objects.ModifyAttributes(obj, obj.Attributes, true);
66 | }
67 |
68 | if (geometryModified)
69 | {
70 | // Update the object geometry if needed
71 | doc.Objects.Transform(obj, xform, true);
72 | }
73 |
74 | // Update views
75 | doc.Views.Redraw();
76 |
77 | return Serializer.RhinoObject(getObjectByIdOrName(new JObject { ["id"] = obj.Id }));
78 |
79 | }
80 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/create_objects.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 |
7 | @mcp.tool()
8 | def create_objects(
9 | ctx: Context,
10 | objects: List[Dict[str, Any]]
11 | ) -> str:
12 | """
13 | Create multiple objects at once in the Rhino document.
14 |
15 | Parameters:
16 | - objects: A list of dictionaries, each containing the parameters for a single object
17 |
18 | Each object should have the following values:
19 | - type: Object type ("POINT", "LINE", "POLYLINE", "BOX", "SPHERE", etc.)
20 | - name: Optional name for the object
21 | - color: Optional [r, g, b] color values (0-255) for the object
22 | - params: Type-specific parameters dictionary (see documentation for each type in create_object() function)
23 | - translation: Optional [x, y, z] translation vector
24 | - rotation: Optional [x, y, z] rotation in radians
25 | - scale: Optional [x, y, z] scale factors
26 |
27 | Returns:
28 | A message indicating the created objects.
29 |
30 | Examples of params:
31 | [
32 | {
33 | "type": "POINT",
34 | "name": "Point 1",
35 | "params": {"x": 0, "y": 0, "z": 0}
36 | },
37 | {
38 | "type": "LINE",
39 | "name": "Line 1",
40 | "params": {"start": [0, 0, 0], "end": [1, 1, 1]}
41 | },
42 | {
43 | "type": "POLYLINE",
44 | "name": "Polyline 1",
45 | "params": {"points": [[0, 0, 0], [1, 1, 1], [2, 2, 2]]}
46 | },
47 | {
48 | "type": "CURVE",
49 | "name": "Curve 1",
50 | "params": {"points": [[0, 0, 0], [1, 1, 1], [2, 2, 2]], "degree": 3}
51 | },
52 | {
53 | "type": "BOX",
54 | "name": "Box 1",
55 | "color": [255, 0, 0],
56 | "params": {"width": 1.0, "length": 1.0, "height": 1.0},
57 | "translation": [0, 0, 0],
58 | "rotation": [0, 0, 0],
59 | "scale": [1, 1, 1]
60 | },
61 | {
62 | "type": "SPHERE",
63 | "name": "Sphere 1",
64 | "color": [0, 255, 0],
65 | "params": {"radius": 1.0},
66 | "translation": [0, 0, 0],
67 | "rotation": [0, 0, 0],
68 | "scale": [1, 1, 1]
69 | }
70 | ]
71 | """
72 | try:
73 | # Get the global connection
74 | rhino = get_rhino_connection()
75 | command_params = {}
76 | for obj in objects:
77 | command_params[obj["name"]] = obj
78 | result = rhino.send_command("create_objects", command_params)
79 |
80 |
81 | return f"Created {len(result)} objects"
82 | except Exception as e:
83 | logger.error(f"Error creating object: {str(e)}")
84 | return f"Error creating object: {str(e)}"
85 |
86 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/static/linetype.py:
--------------------------------------------------------------------------------
```python
1 | import Rhino
2 |
3 | import scriptcontext
4 |
5 | import rhinocompat as compat
6 | from rhinoscript import utility as rhutil
7 |
8 |
9 | def __getlinetype(name_or_id):
10 | id = rhutil.coerceguid(name_or_id)
11 | if id: return scriptcontext.doc.Linetypes.FindId(id)
12 | return scriptcontext.doc.Linetypes.FindName(name_or_id)
13 |
14 |
15 | def IsLinetype(name_or_id):
16 | """Verifies the existance of a linetype in the document
17 | Parameters:
18 | name_or_id (guid|str): The name or identifier of an existing linetype.
19 | Returns:
20 | bool: True or False
21 | Example:
22 | import rhinoscriptsyntax as rs
23 | name = rs.GetString("Linetype name")
24 | if rs.IsLinetype(name): print("The linetype exists.")
25 | else: print("The linetype does not exist")
26 | See Also:
27 | IsLinetypeReference
28 | """
29 | lt = __getlinetype(name_or_id)
30 | return lt is not None
31 |
32 |
33 | def IsLinetypeReference(name_or_id):
34 | """Verifies that an existing linetype is from a reference file
35 | Parameters:
36 | name_or_id (guid|str): The name or identifier of an existing linetype.
37 | Returns:
38 | bool: True or False
39 | Example:
40 | import rhinoscriptsyntax as rs
41 | name = rs.GetString("Linetype name")
42 | if rs.IsLinetype(name):
43 | if rs.IsLinetypeReference(name):
44 | print("The linetype is a reference linetype.")
45 | else:
46 | print("The linetype is not a reference linetype.")
47 | else:
48 | print("The linetype does not exist.")
49 | See Also:
50 | IsLinetype
51 | """
52 | lt = __getlinetype(name_or_id)
53 | if lt is None: raise ValueError("unable to coerce %s into linetype"%name_or_id)
54 | return lt.IsReference
55 |
56 |
57 | def LinetypeCount():
58 | """Returns number of linetypes in the document
59 | Returns:
60 | number: the number of linetypes in the document
61 | Example:
62 | import rhinoscriptsyntax as rs
63 | count = rs.LinetypeCount()
64 | print("There are {} linetypes".format(count))
65 | See Also:
66 | LinetypeNames
67 | """
68 | return scriptcontext.doc.Linetypes.Count
69 |
70 |
71 | def LinetypeNames(sort=False):
72 | """Returns names of all linetypes in the document
73 | Parameters:
74 | sort (bool, optional): return a sorted list of the linetype names
75 | Returns:
76 | list(str, ...): list of linetype names if successful
77 | Example:
78 | import rhinoscriptsyntax as rs
79 | names = rs.LinetypeNames()
80 | if names:
81 | for name in names: print(name)
82 | See Also:
83 | LinetypeCount
84 | """
85 | count = scriptcontext.doc.Linetypes.Count
86 | rc = []
87 | for i in compat.RANGE(count):
88 | linetype = scriptcontext.doc.Linetypes[i]
89 | if not linetype.IsDeleted: rc.append(linetype.Name)
90 | if sort: rc.sort()
91 | return rc
92 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/SelectObjects.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Drawing;
3 | using System.Linq;
4 | using Newtonsoft.Json.Linq;
5 | using Rhino;
6 | using System.Collections.Generic;
7 |
8 | namespace RhinoMCPPlugin.Functions;
9 |
10 | public partial class RhinoMCPFunctions
11 | {
12 | public JObject SelectObjects(JObject parameters)
13 | {
14 | JObject filters = (JObject)parameters["filters"];
15 |
16 | var doc = RhinoDoc.ActiveDoc;
17 | var objects = doc.Objects.ToList();
18 | var selectedObjects = new List<Guid>();
19 | var filtersType = (string)parameters["filters_type"];
20 |
21 | var hasName = false;
22 | var hasColor = false;
23 | var customAttributes = new Dictionary<string, List<string>>();
24 |
25 | // no filter means all are selected
26 | if (filters.Count == 0)
27 | {
28 | doc.Objects.UnselectAll();
29 | doc.Objects.Select(objects.Select(o => o.Id));
30 | doc.Views.Redraw();
31 |
32 | return new JObject() { ["count"] = objects.Count };
33 | }
34 |
35 | foreach (JProperty f in filters.Properties())
36 | {
37 | if (f.Name == "name") hasName = true;
38 | if (f.Name == "color") hasColor = true;
39 | if (f.Name != "name" && f.Name != "color") customAttributes.Add(f.Name, castToStringList(f.Value));
40 | }
41 |
42 | var name = hasName ? castToString(filters.SelectToken("name")) : null;
43 | var color = hasColor ? castToIntArray(filters.SelectToken("color")) : null;
44 |
45 | if (filtersType == "and")
46 | foreach (var obj in objects)
47 | {
48 | var attributeMatch = true;
49 | if (hasName && obj.Name != name) continue;
50 | if (hasColor && obj.Attributes.ObjectColor.R != color[0] && obj.Attributes.ObjectColor.G != color[1] && obj.Attributes.ObjectColor.B != color[2]) continue;
51 | foreach (var customAttribute in customAttributes)
52 | {
53 | foreach (var value in customAttribute.Value)
54 | {
55 | if (obj.Attributes.GetUserString(customAttribute.Key) != value) attributeMatch = false;
56 | }
57 | }
58 | if (!attributeMatch) continue;
59 |
60 | selectedObjects.Add(obj.Id);
61 | }
62 | else if (filtersType == "or")
63 | foreach (var obj in objects)
64 | {
65 | var attributeMatch = false;
66 | if (hasName && obj.Name == name) attributeMatch = true;
67 | if (hasColor && obj.Attributes.ObjectColor.R == color[0] && obj.Attributes.ObjectColor.G == color[1] && obj.Attributes.ObjectColor.B == color[2]) attributeMatch = true;
68 |
69 | foreach (var customAttribute in customAttributes)
70 | {
71 | foreach (var value in customAttribute.Value)
72 | {
73 | if (obj.Attributes.GetUserString(customAttribute.Key) == value) attributeMatch = true;
74 | }
75 | }
76 | if (!attributeMatch) continue;
77 |
78 | selectedObjects.Add(obj.Id);
79 | }
80 |
81 | doc.Objects.UnselectAll();
82 | doc.Objects.Select(selectedObjects);
83 | doc.Views.Redraw();
84 |
85 | return new JObject() { ["count"] = selectedObjects.Count };
86 | }
87 | }
```
--------------------------------------------------------------------------------
/.github/workflows/rhino-plugin-publish.yml:
--------------------------------------------------------------------------------
```yaml
1 | name: Build and Publish Rhino Plugin
2 |
3 | on:
4 | release:
5 | types: [published]
6 |
7 | jobs:
8 | release-build:
9 | runs-on: ubuntu-latest
10 | env:
11 | SOLUTION_PATH: ${{ github.workspace }}/rhino_mcp_plugin/rhinomcp.sln
12 | PROJECT_PATH: ${{ github.workspace }}/rhino_mcp_plugin/rhinomcp.csproj
13 | ARTIFACT_NAME: rhinomcp
14 |
15 | steps:
16 | - name: Checkout code
17 | uses: actions/checkout@v4
18 | with:
19 | fetch-depth: 0
20 |
21 | - name: Setup .NET
22 | uses: actions/setup-dotnet@v4
23 | with:
24 | dotnet-version: '7.0.x'
25 |
26 | - name: Restore NuGet packages
27 | run: |
28 | dotnet restore ${{ env.SOLUTION_PATH }}
29 |
30 | - name: Build solution with MSBuild
31 | run: |
32 | dotnet msbuild ${{ env.SOLUTION_PATH }} /p:Configuration=Release /p:Platform="Any CPU"
33 |
34 | - name: Prepare artifacts
35 | run: |
36 | mkdir -p dist/net7.0
37 | # Copy DLL, RHP, and any other necessary files to the dist folder
38 | cp ${{ github.workspace }}/rhino_mcp_plugin/bin/Release/net7.0/*.dll dist/net7.0/
39 | cp ${{ github.workspace }}/rhino_mcp_plugin/bin/Release/net7.0/*.rhp dist/net7.0/
40 | cp ${{ github.workspace }}/rhino_mcp_plugin/manifest.yml dist/
41 |
42 | - name: Upload build artifacts
43 | uses: actions/upload-artifact@v4
44 | with:
45 | name: ${{ env.ARTIFACT_NAME }}
46 | path: dist/
47 |
48 | publish:
49 | name: Publish to Yak and GitHub Release
50 | runs-on: ubuntu-latest
51 | needs:
52 | - release-build
53 | env:
54 | ARTIFACT_NAME: rhinomcp
55 |
56 | steps:
57 | - name: Download build artifacts
58 | uses: actions/download-artifact@v4
59 | with:
60 | name: ${{ env.ARTIFACT_NAME }}
61 | path: dist
62 |
63 | - name: Setup Yak
64 | run: |
65 | # Create a directory for yak
66 | mkdir -p ${{ github.workspace }}/yakfolder
67 |
68 | # Download Linux version of yak
69 | curl -L "https://files.mcneel.com/yak/tools/0.13.0/linux-x64/yak" -o ${{ github.workspace }}/yakfolder/yak
70 |
71 | # Make it executable
72 | chmod +x ${{ github.workspace }}/yakfolder/yak
73 |
74 | # Add to path and verify
75 | echo "${{ github.workspace }}/yakfolder" >> $GITHUB_PATH
76 | echo "PATH is now: $PATH:${{ github.workspace }}/yakfolder"
77 |
78 | - name: Pack and Push to Yak
79 | run: |
80 | cd dist
81 |
82 | export YAK_TOKEN=${{ secrets.YAK_API_KEY }}
83 |
84 | # Build yak package
85 | yak build
86 |
87 | # List files to verify the .yak file was created
88 | ls -la
89 |
90 | # Find the .yak package
91 | # Use -maxdepth 1 to avoid finding .yak files in subdirectories if any
92 | yakPackageFile=$(find . -maxdepth 1 -name "*.yak" -type f | head -1)
93 |
94 | if [ -z "$yakPackageFile" ]; then
95 | echo "Error: No .yak package was created in the 'dist' directory."
96 | exit 1
97 | fi
98 |
99 | echo "Found package: $yakPackageFile"
100 |
101 | # Get just the filename for the release asset name and GITHUB_ENV
102 | yakPackageBasename=$(basename "$yakPackageFile")
103 | echo "YAK_PACKAGE_BASENAME=$yakPackageBasename" >> $GITHUB_ENV
104 | echo "YAK_PACKAGE_PATH_IN_DIST=$yakPackageFile" >> $GITHUB_ENV # Will be e.g., ./rhinomcp-1.2.3.yak
105 |
106 | # Push to yak server
107 | yak push "$yakPackageFile"
108 |
109 | - name: Upload .yak package to GitHub Release
110 | uses: actions/upload-release-asset@v1
111 | env:
112 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
113 | with:
114 | upload_url: ${{ github.event.release.upload_url }}
115 | asset_path: dist/${{ env.YAK_PACKAGE_PATH_IN_DIST }} # Path relative to GITHUB_WORKSPACE
116 | asset_name: ${{ env.YAK_PACKAGE_BASENAME }}
117 | asset_content_type: application/zip
118 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Serializers/Serializer.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 | using Newtonsoft.Json.Linq;
8 | using Rhino;
9 | using Rhino.DocObjects;
10 | using Rhino.Geometry;
11 |
12 | namespace rhinomcp.Serializers
13 | {
14 | public static class Serializer
15 | {
16 | public static RhinoDoc doc = RhinoDoc.ActiveDoc;
17 |
18 | public static JObject SerializeColor(Color color)
19 | {
20 | return new JObject()
21 | {
22 | ["r"] = color.R,
23 | ["g"] = color.G,
24 | ["b"] = color.B
25 | };
26 | }
27 |
28 | public static JArray SerializePoint(Point3d pt)
29 | {
30 | return new JArray
31 | {
32 | Math.Round(pt.X, 2),
33 | Math.Round(pt.Y, 2),
34 | Math.Round(pt.Z, 2)
35 | };
36 | }
37 |
38 | public static JArray SerializePoints(IEnumerable<Point3d> pts)
39 | {
40 | return new JArray
41 | {
42 | pts.Select(p => SerializePoint(p))
43 | };
44 | }
45 |
46 | public static JObject SerializeCurve(Curve crv)
47 | {
48 | return new JObject
49 | {
50 | ["type"] = "Curve",
51 | ["geometry"] = new JObject
52 | {
53 | ["points"] = SerializePoints(crv.ControlPolygon().ToArray()),
54 | ["degree"] = crv.Degree.ToString()
55 | }
56 | };
57 | }
58 |
59 | public static JArray SerializeBBox(BoundingBox bbox)
60 | {
61 | return new JArray
62 | {
63 | new JArray { bbox.Min.X, bbox.Min.Y, bbox.Min.Z },
64 | new JArray { bbox.Max.X, bbox.Max.Y, bbox.Max.Z }
65 | };
66 | }
67 |
68 | public static JObject SerializeLayer(Layer layer)
69 | {
70 | return new JObject
71 | {
72 | ["id"] = layer.Id.ToString(),
73 | ["name"] = layer.Name,
74 | ["color"] = SerializeColor(layer.Color),
75 | ["parent"] = layer.ParentLayerId.ToString()
76 | };
77 | }
78 |
79 | public static JObject RhinoObjectAttributes(RhinoObject obj)
80 | {
81 | var attributes = obj.Attributes.GetUserStrings();
82 | var attributesDict = new JObject();
83 | foreach (string key in attributes.AllKeys)
84 | {
85 | attributesDict[key] = attributes[key];
86 | }
87 | return attributesDict;
88 | }
89 |
90 | public static JObject RhinoObject(RhinoObject obj)
91 | {
92 | var objInfo = new JObject
93 | {
94 | ["id"] = obj.Id.ToString(),
95 | ["name"] = obj.Name ?? "(unnamed)",
96 | ["type"] = obj.ObjectType.ToString(),
97 | ["layer"] = doc.Layers[obj.Attributes.LayerIndex].Name,
98 | ["material"] = obj.Attributes.MaterialIndex.ToString(),
99 | ["color"] = SerializeColor(obj.Attributes.ObjectColor)
100 | };
101 |
102 | // add boundingbox
103 | BoundingBox bbox = obj.Geometry.GetBoundingBox(true);
104 | objInfo["bounding_box"] = SerializeBBox(bbox);
105 |
106 | // Add geometry data
107 | if (obj.Geometry is Rhino.Geometry.Point point)
108 | {
109 | objInfo["type"] = "POINT";
110 | objInfo["geometry"] = SerializePoint(point.Location);
111 | }
112 | else if (obj.Geometry is Rhino.Geometry.LineCurve line)
113 | {
114 | objInfo["type"] = "LINE";
115 | objInfo["geometry"] = new JObject
116 | {
117 | ["start"] = SerializePoint(line.Line.From),
118 | ["end"] = SerializePoint(line.Line.To)
119 | };
120 | }
121 | else if (obj.Geometry is Rhino.Geometry.PolylineCurve polyline)
122 | {
123 | objInfo["type"] = "POLYLINE";
124 | objInfo["geometry"] = new JObject
125 | {
126 | ["points"] = SerializePoints(polyline.ToArray())
127 | };
128 | }
129 | else if (obj.Geometry is Rhino.Geometry.Curve curve)
130 | {
131 | var crv = SerializeCurve(curve);
132 | objInfo["type"] = crv["type"];
133 | objInfo["geometry"] = crv["geometry"];
134 | }
135 | else if (obj.Geometry is Rhino.Geometry.Extrusion extrusion)
136 | {
137 | objInfo["type"] = "EXTRUSION";
138 | }
139 |
140 |
141 | return objInfo;
142 | }
143 | }
144 | }
145 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/_utils.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Newtonsoft.Json.Linq;
5 | using Rhino;
6 | using Rhino.DocObjects;
7 | using Rhino.Geometry;
8 | using rhinomcp.Serializers;
9 |
10 | namespace RhinoMCPPlugin.Functions;
11 |
12 | public partial class RhinoMCPFunctions
13 | {
14 | private double castToDouble(JToken token)
15 | {
16 | return token?.ToObject<double>() ?? 0;
17 | }
18 | private double[] castToDoubleArray(JToken token)
19 | {
20 | return token?.ToObject<double[]>() ?? new double[] { 0, 0, 0 };
21 | }
22 | private double[][] castToDoubleArray2D(JToken token)
23 | {
24 | List<double[]> result = new List<double[]>();
25 | foreach (var t in (JArray)token)
26 | {
27 | double[] inner = castToDoubleArray(t);
28 | result.Add(inner);
29 | }
30 | return result.ToArray();
31 | }
32 | private int castToInt(JToken token)
33 | {
34 | return token?.ToObject<int>() ?? 0;
35 | }
36 | private int[] castToIntArray(JToken token)
37 | {
38 | return token?.ToObject<int[]>() ?? new int[] { 0, 0, 0 };
39 | }
40 |
41 | private bool[] castToBoolArray(JToken token)
42 | {
43 | return token?.ToObject<bool[]>() ?? new bool[] { false, false };
44 | }
45 |
46 | private List<string> castToStringList(JToken token)
47 | {
48 | return token?.ToObject<List<string>>() ?? new List<string>();
49 | }
50 |
51 | private bool castToBool(JToken token)
52 | {
53 | return token?.ToObject<bool>() ?? false;
54 | }
55 |
56 | private string castToString(JToken token)
57 | {
58 | return token?.ToString();
59 | }
60 |
61 | private Guid castToGuid(JToken token)
62 | {
63 | var guid = token?.ToString();
64 | if (guid == null) return Guid.Empty;
65 | return new Guid(guid);
66 | }
67 |
68 | private List<Point3d> castToPoint3dList(JToken token)
69 | {
70 | double[][] points = castToDoubleArray2D(token);
71 | var ptList = new List<Point3d>();
72 | foreach (var point in points)
73 | {
74 | ptList.Add(new Point3d(point[0], point[1], point[2]));
75 | }
76 | return ptList;
77 | }
78 |
79 | private Point3d castToPoint3d(JToken token)
80 | {
81 | double[] point = castToDoubleArray(token);
82 | return new Point3d(point[0], point[1], point[2]);
83 | }
84 |
85 | private RhinoObject getObjectByIdOrName(JObject parameters)
86 | {
87 | string objectId = parameters["id"]?.ToString();
88 | string objectName = parameters["name"]?.ToString();
89 |
90 | var doc = RhinoDoc.ActiveDoc;
91 | RhinoObject obj = null;
92 |
93 | if (!string.IsNullOrEmpty(objectId))
94 | obj = doc.Objects.Find(new Guid(objectId));
95 | else if (!string.IsNullOrEmpty(objectName))
96 | {
97 | // we assume there's only one of the object with the given name
98 | var objs = doc.Objects.GetObjectList(new ObjectEnumeratorSettings() { NameFilter = objectName }).ToList();
99 | if (objs == null) throw new InvalidOperationException($"Object with name {objectName} not found.");
100 | if (objs.Count > 1) throw new InvalidOperationException($"Multiple objects with name {objectName} found.");
101 | obj = objs[0];
102 | }
103 |
104 | if (obj == null)
105 | throw new InvalidOperationException($"Object with ID {objectId} not found");
106 | return obj;
107 | }
108 |
109 | private Transform applyRotation(JObject parameters, GeometryBase geometry)
110 | {
111 | double[] rotation = parameters["rotation"].ToObject<double[]>();
112 | var xform = Transform.Identity;
113 |
114 | // Calculate the center for rotation
115 | BoundingBox bbox = geometry.GetBoundingBox(true);
116 | Point3d center = bbox.Center;
117 |
118 | // Create rotation transformations (in radians)
119 | Transform rotX = Transform.Rotation(rotation[0], Vector3d.XAxis, center);
120 | Transform rotY = Transform.Rotation(rotation[1], Vector3d.YAxis, center);
121 | Transform rotZ = Transform.Rotation(rotation[2], Vector3d.ZAxis, center);
122 |
123 | // Apply transformations
124 | xform *= rotX;
125 | xform *= rotY;
126 | xform *= rotZ;
127 |
128 | return xform;
129 | }
130 |
131 | private Transform applyTranslation(JObject parameters)
132 | {
133 | double[] translation = parameters["translation"].ToObject<double[]>();
134 | var xform = Transform.Identity;
135 | Vector3d move = new Vector3d(translation[0], translation[1], translation[2]);
136 | xform *= Transform.Translation(move);
137 |
138 | return xform;
139 | }
140 |
141 | private Transform applyScale(JObject parameters, GeometryBase geometry)
142 | {
143 | double[] scale = parameters["scale"].ToObject<double[]>();
144 | var xform = Transform.Identity;
145 |
146 | // Calculate the min for scaling
147 | BoundingBox bbox = geometry.GetBoundingBox(true);
148 | Point3d anchor = bbox.Min;
149 | Plane plane = Plane.WorldXY;
150 | plane.Origin = anchor;
151 |
152 | // Create scale transformation
153 | Transform scaleTransform = Transform.Scale(plane, scale[0], scale[1], scale[2]);
154 | xform *= scaleTransform;
155 |
156 | return xform;
157 | }
158 | }
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/tools/create_object.py:
--------------------------------------------------------------------------------
```python
1 | from mcp.server.fastmcp import Context
2 | import json
3 | from rhinomcp.server import get_rhino_connection, mcp, logger
4 | from typing import Any, List, Dict
5 |
6 | @mcp.tool()
7 | def create_object(
8 | ctx: Context,
9 | type: str = "BOX",
10 | name: str = None,
11 | color: List[int]= None,
12 | params: Dict[str, Any] = {},
13 | translation: List[float]= None,
14 | rotation: List[float]= None,
15 | scale: List[float]= None,
16 | ) -> str:
17 | """
18 | Create a new object in the Rhino document.
19 |
20 | Parameters:
21 | - type: Object type ("POINT", "LINE", "POLYLINE", "CIRCLE", "ARC", "ELLIPSE", "CURVE", "BOX", "SPHERE", "CONE", "CYLINDER", "PIPE", "SURFACE")
22 | - name: Optional name for the object
23 | - color: Optional [r, g, b] color values (0-255) for the object
24 | - params: Type-specific parameters dictionary (see documentation for each type)
25 | - translation: Optional [x, y, z] translation vector
26 | - rotation: Optional [x, y, z] rotation in radians
27 | - scale: Optional [x, y, z] scale factors
28 |
29 | The params dictionary is type-specific.
30 | For POINT, the params dictionary should contain the following keys:
31 | - x: x coordinate of the point
32 | - y: y coordinate of the point
33 | - z: z coordinate of the point
34 |
35 | For LINE, the params dictionary should contain the following keys:
36 | - start: [x, y, z] start point of the line
37 | - end: [x, y, z] end point of the line
38 |
39 | For POLYLINE, the params dictionary should contain the following keys:
40 | - points: List of [x, y, z] points that define the polyline
41 |
42 | For CIRCLE, the params dictionary should contain the following keys:
43 | - center: [x, y, z] center point of the circle
44 | - radius: Radius of the circle
45 |
46 | For ARC, the params dictionary should contain the following keys:
47 | - center: [x, y, z] center point of the arc
48 | - radius: Radius of the arc
49 | - angle: Angle of the arc in degrees
50 |
51 | For ELLIPSE, the params dictionary should contain the following keys:
52 | - center: [x, y, z] center point of the ellipse
53 | - radius_x: Radius of the ellipse along X axis
54 | - radius_y: Radius of the ellipse along Y axis
55 |
56 | For CURVE, the params dictionary should contain the following keys:
57 | - points: List of [x, y, z] control points that define the curve
58 | - degree: Degree of the curve (default is 3, if user asked for smoother curve, degree can be higher)
59 | If the curve is closed, the first and last points should be the same.
60 |
61 | For BOX, the params dictionary should contain the following keys:
62 | - width: Width of the box along X axis of the object
63 | - length: Length of the box along Y axis of the object
64 | - height: Height of the box along Z axis of the object
65 |
66 | For SPHERE, the params dictionary should contain the following key:
67 | - radius: Radius of the sphere
68 |
69 | For CONE, the params dictionary should contain the following keys:
70 | - radius: Radius of the cone
71 | - height: Height of the cone
72 | - cap: Boolean to indicate if the cone should be capped at the base, default is True
73 |
74 | For CYLINDER, the params dictionary should contain the following keys:
75 | - radius: Radius of the cylinder
76 | - height: Height of the cylinder
77 | - cap: Boolean to indicate if the cylinder should be capped at the base, default is True
78 |
79 | For SURFACE, the params dictionary should contain the following keys:
80 | - count : ([number, number]) Tuple of two numbers defining number of points in the u,v directions
81 | - points: List of [x, y, z] points that define the surface
82 | - degree: ([number, number], optional) Degree of the surface (default is 3, if user asked for smoother surface, degree can be higher)
83 | - closed: ([bool, bool], optional) Two booleans defining if the surface is closed in the u,v directions
84 |
85 | Returns:
86 | A message indicating the created object name.
87 |
88 | Examples of params:
89 | - POINT: {"x": 0, "y": 0, "z": 0}
90 | - LINE: {"start": [0, 0, 0], "end": [1, 1, 1]}
91 | - POLYLINE: {"points": [[0, 0, 0], [1, 1, 1], [2, 2, 2]]}
92 | - CIRCLE: {"center": [0, 0, 0], "radius": 1.0}
93 | - CURVE: {"points": [[0, 0, 0], [1, 1, 1], [2, 2, 2]], "degree": 3}
94 | - BOX: {"width": 1.0, "length": 1.0, "height": 1.0}
95 | - SPHERE: {"radius": 1.0}
96 | - CONE: {"radius": 1.0, "height": 1.0, "cap": True}
97 | - CYLINDER: {"radius": 1.0, "height": 1.0, "cap": True}
98 | - SURFACE: {"count": (3, 3), "points": [[0, 0, 0], [1, 0, 0], [2, 0, 0], [0, 1, 0], [1, 1, 0], [2, 1, 0], [0, 2, 0], [1, 2, 0], [2, 2, 0]], "degree": (3, 3), "closed": (False, False)}
99 | """
100 | try:
101 | # Get the global connection
102 | rhino = get_rhino_connection()
103 |
104 | command_params = {
105 | "type": type,
106 | "params": params
107 | }
108 |
109 | if translation is not None: command_params["translation"] = translation
110 | if rotation is not None: command_params["rotation"] = rotation
111 | if scale is not None: command_params["scale"] = scale
112 |
113 | if name: command_params["name"] = name
114 | if color: command_params["color"] = color
115 |
116 | # Create the object
117 | result = result = rhino.send_command("create_object", command_params)
118 |
119 | return f"Created {type} object: {result['name']}"
120 | except Exception as e:
121 | logger.error(f"Error creating object: {str(e)}")
122 | return f"Error creating object: {str(e)}"
123 |
```
--------------------------------------------------------------------------------
/rhino_mcp_plugin/Functions/CreateObject.cs:
--------------------------------------------------------------------------------
```csharp
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Drawing;
4 | using Newtonsoft.Json.Linq;
5 | using Rhino;
6 | using Rhino.DocObjects;
7 | using Rhino.Geometry;
8 | using rhinomcp.Serializers;
9 |
10 | namespace RhinoMCPPlugin.Functions;
11 |
12 | public partial class RhinoMCPFunctions
13 | {
14 | public JObject CreateObject(JObject parameters)
15 | {
16 | // parse meta data
17 | string type = castToString(parameters.SelectToken("type"));
18 | string name = castToString(parameters.SelectToken("name"));
19 | bool customColor = parameters.ContainsKey("color");
20 | int[] color = castToIntArray(parameters.SelectToken("color"));
21 | JObject geoParams = (JObject)parameters.SelectToken("params");
22 |
23 | var doc = RhinoDoc.ActiveDoc;
24 | Guid objectId = Guid.Empty;
25 |
26 | // Create a box centered at the specified point
27 | switch (type)
28 | {
29 | case "POINT":
30 | double x = castToDouble(geoParams.SelectToken("x"));
31 | double y = castToDouble(geoParams.SelectToken("y"));
32 | double z = castToDouble(geoParams.SelectToken("z"));
33 | objectId = doc.Objects.AddPoint(x, y, z);
34 | break;
35 | case "LINE":
36 | double[] start = castToDoubleArray(geoParams.SelectToken("start"));
37 | double[] end = castToDoubleArray(geoParams.SelectToken("end"));
38 | var ptStart = new Point3d(start[0], start[1], start[2]);
39 | var ptEnd = new Point3d(end[0], end[1], end[2]);
40 | objectId = doc.Objects.AddLine(ptStart, ptEnd);
41 | break;
42 | case "POLYLINE":
43 | List<Point3d> ptList = castToPoint3dList(geoParams.SelectToken("points"));
44 | objectId = doc.Objects.AddPolyline(ptList);
45 | break;
46 | case "CIRCLE":
47 | Point3d circleCenter = castToPoint3d(geoParams.SelectToken("center"));
48 | double circleRadius = castToDouble(geoParams.SelectToken("radius"));
49 | var circle = new Circle(circleCenter, circleRadius);
50 | objectId = doc.Objects.AddCircle(circle);
51 | break;
52 | case "ARC":
53 | Point3d arcCenter = castToPoint3d(geoParams.SelectToken("center"));
54 | double arcRadius = castToDouble(geoParams.SelectToken("radius"));
55 | double arcAngle = castToDouble(geoParams.SelectToken("angle"));
56 | var arc = new Arc(new Plane(arcCenter, Vector3d.ZAxis), arcRadius, arcAngle * Math.PI / 180);
57 | objectId = doc.Objects.AddArc(arc);
58 | break;
59 | case "ELLIPSE":
60 | Point3d ellipseCenter = castToPoint3d(geoParams.SelectToken("center"));
61 | double ellipseRadiusX = castToDouble(geoParams.SelectToken("radius_x"));
62 | double ellipseRadiusY = castToDouble(geoParams.SelectToken("radius_y"));
63 | var ellipse = new Ellipse(new Plane(ellipseCenter, Vector3d.ZAxis), ellipseRadiusX, ellipseRadiusY);
64 | objectId = doc.Objects.AddEllipse(ellipse);
65 | break;
66 | case "CURVE":
67 | List<Point3d> controlPoints = castToPoint3dList(geoParams.SelectToken("points"));
68 | int degree = castToInt(geoParams.SelectToken("degree"));
69 | var curve = Curve.CreateControlPointCurve(controlPoints, degree) ?? throw new InvalidOperationException("unable to create control point curve from given points");
70 | objectId = doc.Objects.AddCurve(curve);
71 | break;
72 | case "BOX":
73 | // parse size
74 | double width = castToDouble(geoParams.SelectToken("width"));
75 | double length = castToDouble(geoParams.SelectToken("length"));
76 | double height = castToDouble(geoParams.SelectToken("height"));
77 | double xSize = width, ySize = length, zSize = height;
78 | Box box = new Box(
79 | Plane.WorldXY,
80 | new Interval(-xSize / 2, xSize / 2),
81 | new Interval(-ySize / 2, ySize / 2),
82 | new Interval(-zSize / 2, zSize / 2)
83 | );
84 | objectId = doc.Objects.AddBox(box);
85 | break;
86 |
87 | case "SPHERE":
88 | // parse radius
89 | double radius = castToDouble(geoParams.SelectToken("radius"));
90 | // Create sphere at origin with specified radius
91 | Sphere sphere = new Sphere(Point3d.Origin, radius);
92 | // Convert sphere to BREP for adding to document
93 | objectId = doc.Objects.AddBrep(sphere.ToBrep());
94 | break;
95 | case "CONE":
96 | double coneRadius = castToDouble(geoParams.SelectToken("radius"));
97 | double coneHeight = castToDouble(geoParams.SelectToken("height"));
98 | bool coneCap = castToBool(geoParams.SelectToken("cap"));
99 | Cone cone = new Cone(Plane.WorldXY, coneHeight, coneRadius);
100 | Brep brep = Brep.CreateFromCone(cone, coneCap);
101 | objectId = doc.Objects.AddBrep(brep);
102 | break;
103 | case "CYLINDER":
104 | double cylinderRadius = castToDouble(geoParams.SelectToken("radius"));
105 | double cylinderHeight = castToDouble(geoParams.SelectToken("height"));
106 | bool cylinderCap = castToBool(geoParams.SelectToken("cap"));
107 | Circle cylinderCircle = new Circle(Plane.WorldXY, cylinderRadius);
108 | Cylinder cylinder = new Cylinder(cylinderCircle, cylinderHeight);
109 | objectId = doc.Objects.AddBrep(cylinder.ToBrep(cylinderCap, cylinderCap));
110 | break;
111 | case "SURFACE":
112 | int[] surfaceCount = castToIntArray(geoParams.SelectToken("count"));
113 | List<Point3d> surfacePoints = castToPoint3dList(geoParams.SelectToken("points"));
114 | int[] surfaceDegree = castToIntArray(geoParams.SelectToken("degree"));
115 | bool[] surfaceClosed = castToBoolArray(geoParams.SelectToken("closed"));
116 | var surf = NurbsSurface.CreateThroughPoints(surfacePoints, surfaceCount[0], surfaceCount[1], surfaceDegree[0], surfaceDegree[1], surfaceClosed[0], surfaceClosed[1]);
117 | objectId = doc.Objects.AddSurface(surf);
118 | break;
119 | default:
120 | throw new InvalidOperationException("Invalid object type");
121 | }
122 |
123 | if (objectId == Guid.Empty)
124 | throw new InvalidOperationException("Failed to create object");
125 |
126 | var rhinoObject = doc.Objects.Find(objectId);
127 | if (rhinoObject != null)
128 | {
129 | if (!string.IsNullOrEmpty(name)) rhinoObject.Attributes.Name = name;
130 | if (customColor)
131 | {
132 | rhinoObject.Attributes.ColorSource = ObjectColorSource.ColorFromObject;
133 | rhinoObject.Attributes.ObjectColor = Color.FromArgb(color[0], color[1], color[2]);
134 | }
135 | doc.Objects.ModifyAttributes(rhinoObject, rhinoObject.Attributes, true);
136 | }
137 |
138 | // Update views
139 | doc.Views.Redraw();
140 |
141 | // apply modification
142 | parameters["id"] = objectId;
143 | return ModifyObject(parameters);
144 | }
145 | }
```
--------------------------------------------------------------------------------
/assets/rhinomcp_logo.svg:
--------------------------------------------------------------------------------
```
1 | <svg width="359" height="362" viewBox="0 0 359 362" fill="none" xmlns="http://www.w3.org/2000/svg">
2 | <rect width="359" height="362" fill="#F6F6F6"/>
3 | <path d="M216.5 89.5L140 62.5L116.5 85.5L187 110.187V163.5L116.5 139.155L93 162L187 194.5L251.5 132.5H216.5V89.5Z" fill="#303030" stroke="black"/>
4 | <path d="M30.618 236.361H39.663V242.793H39.797C40.2437 241.721 40.8467 240.738 41.606 239.845C42.3653 238.907 43.2363 238.125 44.219 237.5C45.2017 236.83 46.2513 236.316 47.368 235.959C48.4847 235.602 49.646 235.423 50.852 235.423C51.4773 235.423 52.1697 235.535 52.929 235.758V244.602C52.4823 244.513 51.9463 244.446 51.321 244.401C50.6957 244.312 50.0927 244.267 49.512 244.267C47.77 244.267 46.296 244.557 45.09 245.138C43.884 245.719 42.9013 246.523 42.142 247.55C41.4273 248.533 40.9137 249.694 40.601 251.034C40.2883 252.374 40.132 253.826 40.132 255.389V271H30.618V236.361ZM56.659 223.162H66.173V241.185H66.374C67.58 239.175 69.121 237.723 70.997 236.83C72.873 235.892 74.7043 235.423 76.491 235.423C79.037 235.423 81.114 235.78 82.722 236.495C84.3747 237.165 85.67 238.125 86.608 239.376C87.546 240.582 88.1937 242.078 88.551 243.865C88.953 245.607 89.154 247.55 89.154 249.694V271H79.64V251.436C79.64 248.577 79.1933 246.456 78.3 245.071C77.4067 243.642 75.821 242.927 73.543 242.927C70.9523 242.927 69.0763 243.709 67.915 245.272C66.7537 246.791 66.173 249.314 66.173 252.843V271H56.659V223.162ZM106.157 231.001H96.6428V223.162H106.157V231.001ZM96.6428 236.361H106.157V271H96.6428V236.361ZM113.648 236.361H122.693V241.185H122.894C124.1 239.175 125.664 237.723 127.584 236.83C129.505 235.892 131.47 235.423 133.48 235.423C136.026 235.423 138.103 235.78 139.711 236.495C141.364 237.165 142.659 238.125 143.597 239.376C144.535 240.582 145.183 242.078 145.54 243.865C145.942 245.607 146.143 247.55 146.143 249.694V271H136.629V251.436C136.629 248.577 136.183 246.456 135.289 245.071C134.396 243.642 132.81 242.927 130.532 242.927C127.942 242.927 126.066 243.709 124.904 245.272C123.743 246.791 123.162 249.314 123.162 252.843V271H113.648V236.361ZM161.806 253.714C161.806 255.099 161.94 256.461 162.208 257.801C162.476 259.096 162.923 260.28 163.548 261.352C164.218 262.379 165.089 263.206 166.161 263.831C167.233 264.456 168.573 264.769 170.181 264.769C171.789 264.769 173.129 264.456 174.201 263.831C175.318 263.206 176.189 262.379 176.814 261.352C177.484 260.28 177.953 259.096 178.221 257.801C178.489 256.461 178.623 255.099 178.623 253.714C178.623 252.329 178.489 250.967 178.221 249.627C177.953 248.287 177.484 247.103 176.814 246.076C176.189 245.049 175.318 244.222 174.201 243.597C173.129 242.927 171.789 242.592 170.181 242.592C168.573 242.592 167.233 242.927 166.161 243.597C165.089 244.222 164.218 245.049 163.548 246.076C162.923 247.103 162.476 248.287 162.208 249.627C161.94 250.967 161.806 252.329 161.806 253.714ZM152.292 253.714C152.292 250.945 152.716 248.443 153.565 246.21C154.414 243.932 155.62 242.011 157.183 240.448C158.746 238.84 160.622 237.612 162.811 236.763C165 235.87 167.456 235.423 170.181 235.423C172.906 235.423 175.362 235.87 177.551 236.763C179.784 237.612 181.683 238.84 183.246 240.448C184.809 242.011 186.015 243.932 186.864 246.21C187.713 248.443 188.137 250.945 188.137 253.714C188.137 256.483 187.713 258.985 186.864 261.218C186.015 263.451 184.809 265.372 183.246 266.98C181.683 268.543 179.784 269.749 177.551 270.598C175.362 271.447 172.906 271.871 170.181 271.871C167.456 271.871 165 271.447 162.811 270.598C160.622 269.749 158.746 268.543 157.183 266.98C155.62 265.372 154.414 263.451 153.565 261.218C152.716 258.985 152.292 256.483 152.292 253.714ZM194.591 236.361H203.569V241.051H203.703C204.954 239.264 206.45 237.88 208.192 236.897C209.979 235.914 212.011 235.423 214.289 235.423C216.478 235.423 218.465 235.847 220.252 236.696C222.083 237.545 223.468 239.041 224.406 241.185C225.433 239.666 226.818 238.326 228.56 237.165C230.347 236.004 232.446 235.423 234.858 235.423C236.689 235.423 238.387 235.646 239.95 236.093C241.513 236.54 242.853 237.254 243.97 238.237C245.087 239.22 245.958 240.515 246.583 242.123C247.208 243.686 247.521 245.585 247.521 247.818V271H238.007V251.369C238.007 250.208 237.962 249.113 237.873 248.086C237.784 247.059 237.538 246.165 237.136 245.406C236.734 244.647 236.131 244.044 235.327 243.597C234.568 243.15 233.518 242.927 232.178 242.927C230.838 242.927 229.744 243.195 228.895 243.731C228.091 244.222 227.443 244.892 226.952 245.741C226.505 246.545 226.193 247.483 226.014 248.555C225.88 249.582 225.813 250.632 225.813 251.704V271H216.299V251.57C216.299 250.543 216.277 249.538 216.232 248.555C216.187 247.528 215.986 246.59 215.629 245.741C215.316 244.892 214.758 244.222 213.954 243.731C213.195 243.195 212.056 242.927 210.537 242.927C210.09 242.927 209.487 243.039 208.728 243.262C208.013 243.441 207.299 243.82 206.584 244.401C205.914 244.937 205.333 245.741 204.842 246.813C204.351 247.84 204.105 249.203 204.105 250.9V271H194.591V236.361ZM278.626 248.555C278 244.58 275.655 242.592 271.591 242.592C270.072 242.592 268.799 242.949 267.772 243.664C266.744 244.334 265.896 245.227 265.226 246.344C264.6 247.416 264.154 248.622 263.886 249.962C263.618 251.257 263.484 252.553 263.484 253.848C263.484 255.099 263.618 256.372 263.886 257.667C264.154 258.962 264.578 260.146 265.159 261.218C265.784 262.245 266.61 263.094 267.638 263.764C268.665 264.434 269.916 264.769 271.39 264.769C273.668 264.769 275.41 264.144 276.616 262.893C277.866 261.598 278.648 259.878 278.961 257.734H288.14C287.514 262.335 285.728 265.841 282.78 268.253C279.832 270.665 276.057 271.871 271.457 271.871C268.866 271.871 266.476 271.447 264.288 270.598C262.144 269.705 260.312 268.476 258.794 266.913C257.275 265.35 256.091 263.496 255.243 261.352C254.394 259.163 253.97 256.774 253.97 254.183C253.97 251.503 254.349 249.024 255.109 246.746C255.913 244.423 257.074 242.436 258.593 240.783C260.111 239.086 261.965 237.768 264.154 236.83C266.342 235.892 268.844 235.423 271.658 235.423C273.712 235.423 275.678 235.691 277.554 236.227C279.474 236.763 281.172 237.589 282.646 238.706C284.164 239.778 285.393 241.14 286.331 242.793C287.269 244.401 287.805 246.322 287.939 248.555H278.626ZM311.001 264.769C312.565 264.769 313.86 264.456 314.887 263.831C315.959 263.206 316.808 262.402 317.433 261.419C318.103 260.392 318.572 259.208 318.84 257.868C319.108 256.528 319.242 255.166 319.242 253.781C319.242 252.396 319.086 251.034 318.773 249.694C318.505 248.354 318.036 247.17 317.366 246.143C316.696 245.071 315.825 244.222 314.753 243.597C313.726 242.927 312.453 242.592 310.934 242.592C309.371 242.592 308.053 242.927 306.981 243.597C305.954 244.222 305.105 245.049 304.435 246.076C303.81 247.103 303.363 248.287 303.095 249.627C302.827 250.967 302.693 252.352 302.693 253.781C302.693 255.166 302.827 256.528 303.095 257.868C303.408 259.208 303.877 260.392 304.502 261.419C305.172 262.402 306.043 263.206 307.115 263.831C308.187 264.456 309.483 264.769 311.001 264.769ZM293.514 236.361H302.559V240.783H302.693C303.855 238.907 305.329 237.545 307.115 236.696C308.902 235.847 310.867 235.423 313.011 235.423C315.736 235.423 318.081 235.937 320.046 236.964C322.012 237.991 323.642 239.354 324.937 241.051C326.233 242.748 327.193 244.736 327.818 247.014C328.444 249.247 328.756 251.592 328.756 254.049C328.756 256.372 328.444 258.605 327.818 260.749C327.193 262.893 326.233 264.791 324.937 266.444C323.687 268.097 322.101 269.414 320.18 270.397C318.304 271.38 316.093 271.871 313.547 271.871C311.403 271.871 309.416 271.447 307.584 270.598C305.798 269.705 304.324 268.409 303.162 266.712H303.028V283.127H293.514V236.361Z" fill="#303030"/>
5 | </svg>
6 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/src/rhinomcp/server.py:
--------------------------------------------------------------------------------
```python
1 | # rhino_mcp_server.py
2 | from mcp.server.fastmcp import FastMCP, Context, Image
3 | import socket
4 | import json
5 | import asyncio
6 | import logging
7 | from dataclasses import dataclass
8 | from contextlib import asynccontextmanager
9 | from typing import AsyncIterator, Dict, Any, List
10 |
11 | # Configure logging
12 | logging.basicConfig(level=logging.INFO,
13 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
14 | logger = logging.getLogger("RhinoMCPServer")
15 |
16 | @dataclass
17 | class RhinoConnection:
18 | host: str
19 | port: int
20 | sock: socket.socket | None = None # Changed from 'socket' to 'sock' to avoid naming conflict
21 |
22 | def connect(self) -> bool:
23 | """Connect to the Rhino addon socket server"""
24 | if self.sock:
25 | return True
26 |
27 | try:
28 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
29 | self.sock.connect((self.host, self.port))
30 | logger.info(f"Connected to Rhino at {self.host}:{self.port}")
31 | return True
32 | except Exception as e:
33 | logger.error(f"Failed to connect to Rhino: {str(e)}")
34 | self.sock = None
35 | return False
36 |
37 | def disconnect(self):
38 | """Disconnect from the Rhino addon"""
39 | if self.sock:
40 | try:
41 | self.sock.close()
42 | except Exception as e:
43 | logger.error(f"Error disconnecting from Rhino: {str(e)}")
44 | finally:
45 | self.sock = None
46 |
47 | def receive_full_response(self, sock, buffer_size=8192):
48 | """Receive the complete response, potentially in multiple chunks"""
49 | chunks = []
50 | # Use a consistent timeout value that matches the addon's timeout
51 | sock.settimeout(15.0) # Match the addon's timeout
52 |
53 | try:
54 | while True:
55 | try:
56 | chunk = sock.recv(buffer_size)
57 | if not chunk:
58 | # If we get an empty chunk, the connection might be closed
59 | if not chunks: # If we haven't received anything yet, this is an error
60 | raise Exception("Connection closed before receiving any data")
61 | break
62 |
63 | chunks.append(chunk)
64 |
65 | # Check if we've received a complete JSON object
66 | try:
67 | data = b''.join(chunks)
68 | json.loads(data.decode('utf-8'))
69 | # If we get here, it parsed successfully
70 | logger.info(f"Received complete response ({len(data)} bytes)")
71 | return data
72 | except json.JSONDecodeError:
73 | # Incomplete JSON, continue receiving
74 | continue
75 | except socket.timeout:
76 | # If we hit a timeout during receiving, break the loop and try to use what we have
77 | logger.warning("Socket timeout during chunked receive")
78 | break
79 | except (ConnectionError, BrokenPipeError, ConnectionResetError) as e:
80 | logger.error(f"Socket connection error during receive: {str(e)}")
81 | raise # Re-raise to be handled by the caller
82 | except socket.timeout:
83 | logger.warning("Socket timeout during chunked receive")
84 | except Exception as e:
85 | logger.error(f"Error during receive: {str(e)}")
86 | raise
87 |
88 | # If we get here, we either timed out or broke out of the loop
89 | # Try to use what we have
90 | if chunks:
91 | data = b''.join(chunks)
92 | logger.info(f"Returning data after receive completion ({len(data)} bytes)")
93 | try:
94 | # Try to parse what we have
95 | json.loads(data.decode('utf-8'))
96 | return data
97 | except json.JSONDecodeError:
98 | # If we can't parse it, it's incomplete
99 | raise Exception("Incomplete JSON response received")
100 | else:
101 | raise Exception("No data received")
102 |
103 | def send_command(self, command_type: str, params: Dict[str, Any] = {}) -> Dict[str, Any]:
104 | """Send a command to Rhino and return the response"""
105 | if not self.sock and not self.connect():
106 | raise ConnectionError("Not connected to Rhino")
107 |
108 | command = {
109 | "type": command_type,
110 | "params": params or {}
111 | }
112 |
113 | try:
114 | # Log the command being sent
115 | logger.info(f"Sending command: {command_type} with params: {params}")
116 |
117 | if self.sock is None:
118 | raise Exception("Socket is not connected")
119 |
120 | # Send the command
121 | self.sock.sendall(json.dumps(command).encode('utf-8'))
122 | logger.info(f"Command sent, waiting for response...")
123 |
124 | # Set a timeout for receiving - use the same timeout as in receive_full_response
125 | self.sock.settimeout(15.0) # Match the addon's timeout
126 |
127 | # Receive the response using the improved receive_full_response method
128 | response_data = self.receive_full_response(self.sock)
129 | logger.info(f"Received {len(response_data)} bytes of data")
130 |
131 | response = json.loads(response_data.decode('utf-8'))
132 | logger.info(f"Response parsed, status: {response.get('status', 'unknown')}")
133 |
134 | if response.get("status") == "error":
135 | logger.error(f"Rhino error: {response.get('message')}")
136 | raise Exception(response.get("message", "Unknown error from Rhino"))
137 |
138 | return response.get("result", {})
139 | except socket.timeout:
140 | logger.error("Socket timeout while waiting for response from Rhino")
141 | # Don't try to reconnect here - let the get_rhino_connection handle reconnection
142 | # Just invalidate the current socket so it will be recreated next time
143 | self.sock = None
144 | raise Exception("Timeout waiting for Rhino response - try simplifying your request")
145 | except (ConnectionError, BrokenPipeError, ConnectionResetError) as e:
146 | logger.error(f"Socket connection error: {str(e)}")
147 | self.sock = None
148 | raise Exception(f"Connection to Rhino lost: {str(e)}")
149 | except json.JSONDecodeError as e:
150 | logger.error(f"Invalid JSON response from Rhino: {str(e)}")
151 | # Try to log what was received
152 | if 'response_data' in locals() and response_data: # type: ignore
153 | logger.error(f"Raw response (first 200 bytes): {response_data[:200]}")
154 | raise Exception(f"Invalid response from Rhino: {str(e)}")
155 | except Exception as e:
156 | logger.error(f"Error communicating with Rhino: {str(e)}")
157 | # Don't try to reconnect here - let the get_rhino_connection handle reconnection
158 | self.sock = None
159 | raise Exception(f"Communication error with Rhino: {str(e)}")
160 |
161 | @asynccontextmanager
162 | async def server_lifespan(server: FastMCP) -> AsyncIterator[Dict[str, Any]]:
163 | """Manage server startup and shutdown lifecycle"""
164 | # We don't need to create a connection here since we're using the global connection
165 | # for resources and tools
166 |
167 | try:
168 | # Just log that we're starting up
169 | logger.info("RhinoMCP server starting up")
170 |
171 | # Try to connect to Rhino on startup to verify it's available
172 | try:
173 | # This will initialize the global connection if needed
174 | rhino = get_rhino_connection()
175 | logger.info("Successfully connected to Rhino on startup")
176 | except Exception as e:
177 | logger.warning(f"Could not connect to Rhino on startup: {str(e)}")
178 | logger.warning("Make sure the Rhino addon is running before using Rhino resources or tools")
179 |
180 | # Return an empty context - we're using the global connection
181 | yield {}
182 | finally:
183 | # Clean up the global connection on shutdown
184 | global _rhino_connection
185 | if _rhino_connection:
186 | logger.info("Disconnecting from Rhino on shutdown")
187 | _rhino_connection.disconnect()
188 | _rhino_connection = None
189 | logger.info("RhinoMCP server shut down")
190 |
191 | # Create the MCP server with lifespan support
192 | mcp = FastMCP(
193 | "RhinoMCP",
194 | lifespan=server_lifespan
195 | )
196 |
197 | # Resource endpoints
198 |
199 | # Global connection for resources (since resources can't access context)
200 | _rhino_connection = None
201 |
202 | def get_rhino_connection():
203 | """Get or create a persistent Rhino connection"""
204 | global _rhino_connection
205 |
206 | # Create a new connection if needed
207 | if _rhino_connection is None:
208 | _rhino_connection = RhinoConnection(host="127.0.0.1", port=1999)
209 | if not _rhino_connection.connect():
210 | logger.error("Failed to connect to Rhino")
211 | _rhino_connection = None
212 | raise Exception("Could not connect to Rhino. Make sure the Rhino addon is running.")
213 | logger.info("Created new persistent connection to Rhino")
214 |
215 | return _rhino_connection
216 |
217 | # Main execution
218 | def main():
219 | """Run the MCP server"""
220 | mcp.run()
221 |
222 |
223 | if __name__ == "__main__":
224 | main()
```
--------------------------------------------------------------------------------
/rhino_mcp_server/static/userdata.py:
--------------------------------------------------------------------------------
```python
1 | import scriptcontext
2 |
3 | from rhinoscript import utility as rhutil
4 |
5 |
6 | def DeleteDocumentData(section=None, entry=None):
7 | """Removes user data strings from the current document
8 | Parameters:
9 | section (str, optional): section name. If omitted, all sections and their corresponding
10 | entries are removed
11 | entry (str, optional): entry name. If omitted, all entries for section are removed
12 | Returns:
13 | bool: True or False indicating success or failure
14 | Example:
15 | import rhinoscriptsyntax as rs
16 | rs.DeleteDocumentData( "MySection1", "MyEntry1" )
17 | rs.DeleteDocumentData( "MySection1", "MyEntry2" )
18 | rs.DeleteDocumentData( "MySection2", "MyEntry1" )
19 | See Also:
20 | DocumentDataCount
21 | GetDocumentData
22 | IsDocumentData
23 | SetDocumentData
24 | """
25 | return scriptcontext.doc.Strings.Delete(section, entry)
26 |
27 |
28 | def DocumentDataCount():
29 | """Returns the number of user data strings in the current document
30 | Returns:
31 | number: the number of user data strings in the current document
32 | Example:
33 | import rhinoscriptsyntax as rs
34 | count = rs.DocumentDataCount()
35 | print("RhinoScript document user data count: {}".format(count))
36 | See Also:
37 | DeleteDocumentData
38 | GetDocumentData
39 | IsDocumentData
40 | SetDocumentData
41 | """
42 | return scriptcontext.doc.Strings.DocumentDataCount
43 |
44 |
45 | def DocumentUserTextCount():
46 | """Returns the number of user text strings in the current document
47 | Returns:
48 | number: the number of user text strings in the current document
49 | Example:
50 |
51 | See Also:
52 | GetDocumentUserText
53 | IsDocumentUserText
54 | SetDocumentUserText
55 | """
56 | return scriptcontext.doc.Strings.DocumentUserTextCount
57 |
58 |
59 | def GetDocumentData(section=None, entry=None):
60 | """Returns a user data item from the current document
61 | Parameters:
62 | section (str, optional): section name. If omitted, all section names are returned
63 | entry (str, optional): entry name. If omitted, all entry names for section are returned
64 | Returns:
65 | list(str, ...): of all section names if section name is omitted
66 | list(str, ...) of all entry names for a section if entry is omitted
67 | str: value of the entry if both section and entry are specified
68 | None: if not successful
69 | Example:
70 | import rhinoscriptsyntax as rs
71 | value = rs.GetDocumentData("MySection1", "MyEntry1")
72 | print(value)
73 | value = rs.GetDocumentData("MySection1", "MyEntry2")
74 | print(value)
75 | value = rs.GetDocumentData("MySection2", "MyEntry1")
76 | print(value)
77 | See Also:
78 | DeleteDocumentData
79 | DocumentDataCount
80 | IsDocumentData
81 | SetDocumentData
82 | """
83 | if section is None:
84 | rc = scriptcontext.doc.Strings.GetSectionNames()
85 | return list(rc) if rc else None
86 | if entry is None:
87 | rc = scriptcontext.doc.Strings.GetEntryNames(section)
88 | return list(rc) if rc else None
89 | val = scriptcontext.doc.Strings.GetValue(section, entry)
90 | return val if val else None
91 |
92 |
93 | def GetDocumentUserText(key=None):
94 | """Returns user text stored in the document
95 | Parameters:
96 | key (str, optional): key to use for retrieving user text. If empty, all keys are returned
97 | Returns:
98 | str: If key is specified, then the associated value if successful.
99 | list(str, ...):If key is not specified, then a list of key names if successful.
100 | None: If not successful, or on error.
101 | Example:
102 | import rhinoscriptsyntax as rs
103 | print(rs.GetDocumentUserText("Designer"))
104 | print(rs.GetDocumentUserText("Notes"))
105 | See Also:
106 | SetDocumentUserText
107 | """
108 | if key:
109 | val = scriptcontext.doc.Strings.GetValue(key)
110 | return val if val else None
111 | #todo: leaky abstraction: "\\" logic should be inside doc.Strings implementation
112 | keys = [scriptcontext.doc.Strings.GetKey(i) for i in range(scriptcontext.doc.Strings.Count) if not "\\" in scriptcontext.doc.Strings.GetKey(i)]
113 | return keys if keys else None
114 |
115 |
116 | def GetUserText(object_id, key=None, attached_to_geometry=False):
117 | """Returns user text stored on an object.
118 | Parameters:
119 | object_id (guid): the object's identifies
120 | key (str, optional): the key name. If omitted all key names for an object are returned
121 | attached_to_geometry (bool, optional): location on the object to retrieve the user text
122 | Returns:
123 | str: if key is specified, the associated value if successful
124 | list(str, ...): if key is not specified, a list of key names if successful
125 | Example:
126 | import rhinoscriptsyntax as rs
127 | obj = rs.GetObject("Select object")
128 | if obj:
129 | print(rs.GetUserText(obj, "PartNo"))
130 | print(rs.GetUserText(obj, "Price"))
131 | See Also:
132 | IsUserText
133 | SetUserText
134 | """
135 | obj = rhutil.coercerhinoobject(object_id, True, True)
136 | source = None
137 | if attached_to_geometry: source = obj.Geometry
138 | else: source = obj.Attributes
139 | rc = None
140 | if key: return source.GetUserString(key)
141 | userstrings = source.GetUserStrings()
142 | return [userstrings.GetKey(i) for i in range(userstrings.Count)]
143 |
144 |
145 | def IsDocumentData():
146 | """Verifies the current document contains user data
147 | Returns:
148 | bool: True or False indicating the presence of Script user data
149 | Example:
150 | import rhinoscriptsyntax as rs
151 | result = rs.IsDocumentData()
152 | if result:
153 | print("This document contains Script document user data")
154 | else:
155 | print("This document contains no Script document user data")
156 | See Also:
157 | DeleteDocumentData
158 | DocumentDataCount
159 | GetDocumentData
160 | SetDocumentData
161 | """
162 | return scriptcontext.doc.Strings.DocumentDataCount > 0
163 |
164 |
165 | def IsDocumentUserText():
166 | """Verifies the current document contains user text
167 | Returns:
168 | bool: True or False indicating the presence of Script user text
169 | Example:
170 |
171 | See Also:
172 | GetDocumentUserText
173 | SetDocumentUserText
174 | """
175 | return scriptcontext.doc.Strings.DocumentUserTextCount > 0
176 |
177 |
178 | def IsUserText(object_id):
179 | """Verifies that an object contains user text
180 | Parameters:
181 | object_id (guid): the object's identifier
182 | Returns:
183 | number: result of test:
184 | 0 = no user text
185 | 1 = attribute user text
186 | 2 = geometry user text
187 | 3 = both attribute and geometry user text
188 | Example:
189 | import rhinoscriptsyntax as rs
190 | obj = rs.GetObject("Select object")
191 | if obj:
192 | usertext_type = rs.IsUserText(obj)
193 | if usertext_type==0: print("Object has no user text")
194 | elif usertext_type==1: print("Object has attribute user text")
195 | elif usertext_type==2: print("Object has geometry user text")
196 | elif usertext_type==3: print("Object has attribute and geometry user text")
197 | else: print("Object does not exist")
198 | See Also:
199 | GetUserText
200 | SetUserText
201 | """
202 | obj = rhutil.coercerhinoobject(object_id, True, True)
203 | rc = 0
204 | if obj.Attributes.UserStringCount: rc = rc|1
205 | if obj.Geometry.UserStringCount: rc = rc|2
206 | return rc
207 |
208 |
209 | def SetDocumentData(section, entry, value):
210 | """Adds or sets a user data string to the current document
211 | Parameters:
212 | section (str): the section name
213 | entry (str): the entry name
214 | value (str): the string value
215 | Returns:
216 | str: The previous value
217 | Example:
218 | import rhinoscriptsyntax as rs
219 | rs.SetDocumentData( "MySection1", "MyEntry1", "MyValue1" )
220 | rs.SetDocumentData( "MySection1", "MyEntry2", "MyValue2" )
221 | rs.SetDocumentData( "MySection2", "MyEntry1", "MyValue1" )
222 | See Also:
223 | DeleteDocumentData
224 | DocumentDataCount
225 | GetDocumentData
226 | IsDocumentData
227 | """
228 | val = scriptcontext.doc.Strings.SetString(section, entry, value)
229 | return val if val else None
230 |
231 |
232 | def SetDocumentUserText(key, value=None):
233 | """Sets or removes user text stored in the document
234 | Parameters:
235 | key (str): key name to set
236 | value (str): The string value to set. If omitted the key/value pair
237 | specified by key will be deleted
238 | Returns:
239 | bool: True or False indicating success
240 | Example:
241 | import rhinoscriptsyntax as rs
242 | rs.SetDocumentUserText("Designer", "Steve Baer")
243 | rs.SetDocumentUserText("Notes", "Added some layer and updated some geometry")
244 | See Also:
245 | GetDocumentUserText
246 | """
247 | if value: scriptcontext.doc.Strings.SetString(key,value)
248 | else: scriptcontext.doc.Strings.Delete(key)
249 | return True
250 |
251 |
252 | def SetUserText(object_id, key, value=None, attach_to_geometry=False):
253 | """Sets or removes user text stored on an object.
254 | Parameters:
255 | object_id (str): the object's identifier
256 | key (str): the key name to set
257 | value (str, optional) the string value to set. If omitted, the key/value pair
258 | specified by key will be deleted
259 | attach_to_geometry (bool, optional): location on the object to store the user text
260 | Returns:
261 | bool: True or False indicating success or failure
262 | Example:
263 | import rhinoscriptsyntax as rs
264 | obj = rs.GetObject("Select object")
265 | if obj:
266 | rs.SetUserText( obj, "PartNo", "KM40-4960" )
267 | rs.SetUserText( obj, "Price", "1.25" )
268 | See Also:
269 | GetUserText
270 | IsUserText
271 | """
272 | obj = rhutil.coercerhinoobject(object_id, True, True)
273 | if type(key) is not str: key = str(key)
274 | if value and type(value) is not str: value = str(value)
275 | if attach_to_geometry: return obj.Geometry.SetUserString(key, value)
276 | return obj.Attributes.SetUserString(key, value)
```
--------------------------------------------------------------------------------
/rhino_mcp_server/static/line.py:
--------------------------------------------------------------------------------
```python
1 | import Rhino
2 |
3 | import scriptcontext
4 |
5 | import rhinocompat as compat
6 | from rhinoscript import utility as rhutil
7 |
8 |
9 | def LineClosestPoint(line, testpoint):
10 | """Finds the point on an infinite line that is closest to a test point
11 | Parameters:
12 | line ([point, point]): List of 6 numbers or 2 Point3d. Two 3-D points identifying the starting and ending points of the line.
13 | testpoint (point): List of 3 numbers or Point3d. The test point.
14 | Returns:
15 | point: the point on the line that is closest to the test point if successful, otherwise None
16 | Example:
17 | import rhinoscriptsyntax as rs
18 | line = (0,0,0), (5,5,0)
19 | point = (15, 10, 0)
20 | result = rs.LineClosestPoint( line, point)
21 | if result: rs.AddPoint(result)
22 | See Also:
23 | LineIsFartherThan
24 | LineMaxDistanceTo
25 | LineMinDistanceTo
26 | LinePlane
27 | LineTransform
28 | """
29 | line = rhutil.coerceline(line, True)
30 | testpoint = rhutil.coerce3dpoint(testpoint, True)
31 | return line.ClosestPoint(testpoint, False)
32 |
33 |
34 | def LineCylinderIntersection(line, cylinder_plane, cylinder_height, cylinder_radius):
35 | """Calculates the intersection of a line and a cylinder
36 | Parameters:
37 | line (guid|line): the line to intersect
38 | cylinder_plane (plane): base plane of the cylinder
39 | cylinder_height (number): height of the cylinder
40 | cylinder_radius (number): radius of the cylinder
41 | Returns:
42 | list(point, ...): list of intersection points (0, 1, or 2 points)
43 | Example:
44 | import rhinoscriptsyntax as rs
45 | plane = rs.WorldXYPlane()
46 | line = (-10,0,0), (10,0,10)
47 | points = rs.LineCylinderIntersection(line, plane, cylinder_height=10, cylinder_radius=5)
48 | if points:
49 | for point in points: rs.AddPoint(point)
50 | See Also:
51 | LineLineIntersection
52 | LinePlaneIntersection
53 | LineSphereIntersection
54 | """
55 | line = rhutil.coerceline(line, True)
56 | cylinder_plane = rhutil.coerceplane(cylinder_plane, True)
57 | circle = Rhino.Geometry.Circle( cylinder_plane, cylinder_radius )
58 | if not circle.IsValid: raise ValueError("unable to create valid circle with given plane and radius")
59 | cyl = Rhino.Geometry.Cylinder( circle, cylinder_height )
60 | if not cyl.IsValid: raise ValueError("unable to create valid cylinder with given circle and height")
61 | rc, pt1, pt2 = Rhino.Geometry.Intersect.Intersection.LineCylinder(line, cyl)
62 | if rc==compat.ENUM_NONE(Rhino.Geometry.Intersect.LineCylinderIntersection):
63 | return []
64 | if rc==Rhino.Geometry.Intersect.LineCylinderIntersection.Single:
65 | return [pt1]
66 | return [pt1, pt2]
67 |
68 |
69 | def LineIsFartherThan(line, distance, point_or_line):
70 | """Determines if the shortest distance from a line to a point or another
71 | line is greater than a specified distance
72 | Parameters:
73 | line (line | [point, point]): List of 6 numbers, 2 Point3d, or Line.
74 | distance (number): the distance
75 | point_or_line (point|line) the test point or the test line
76 | Returns:
77 | bool: True if the shortest distance from the line to the other project is
78 | greater than distance, False otherwise
79 | None: on error
80 | Example:
81 | import rhinoscriptsyntax as rs
82 | line = (0,0,0), (10,10,0)
83 | testPoint = (10,5,0)
84 | print(rs.LineIsFartherThan(line, 3, testPoint))
85 | See Also:
86 | LineClosestPoint
87 | LineMaxDistanceTo
88 | LineMinDistanceTo
89 | LinePlane
90 | LineTransform
91 | """
92 | line = rhutil.coerceline(line, True)
93 | test = rhutil.coerceline(point_or_line)
94 | if not test: test = rhutil.coerce3dpoint(point_or_line, True)
95 | minDist = line.MinimumDistanceTo(test)
96 | return minDist>distance
97 |
98 |
99 | def LineLineIntersection(lineA, lineB):
100 | """Calculates the intersection of two non-parallel lines. Note, the two
101 | lines do not have to intersect for an intersection to be found. (see help)
102 | Parameters:
103 | lineA, lineB (line): lines to intersect
104 | Returns:
105 | tuple(point, point): containing a point on the first line and a point on the second line if successful
106 | None: on error
107 | Example:
108 | import rhinoscriptsyntax as rs
109 | lineA = (1,1,0), (5,0,0)
110 | lineB = (1,3,0), (5,5,0)
111 | point = rs.LineLineIntersection(lineA, lineB)
112 | if point:
113 | rs.AddPoint(point[0])
114 | rs.AddPoint(point[1])
115 | See Also:
116 | IntersectPlanes
117 | LinePlaneIntersection
118 | PlanePlaneIntersection
119 | """
120 | lineA = rhutil.coerceline(lineA, True)
121 | lineB = rhutil.coerceline(lineB, True)
122 | rc, a, b = Rhino.Geometry.Intersect.Intersection.LineLine(lineA, lineB)
123 | if not rc: return None
124 | return lineA.PointAt(a), lineB.PointAt(b)
125 |
126 |
127 | def LineMaxDistanceTo(line, point_or_line):
128 | """Finds the longest distance between a line as a finite chord, and a point
129 | or another line
130 | Parameters:
131 | line (line | [point, point]): List of 6 numbers, two Point3d, or Line.
132 | point_or_line (point|line): the test point or test line.
133 | Returns:
134 | number: A distance (D) such that if Q is any point on the line and P is any point on the other object, then D >= Rhino.Distance(Q, P).
135 | None: on error
136 | Example:
137 | import rhinoscriptsyntax as rs
138 | line = (0,0,0), (10,10,0)
139 | print(rs.LineMaxDistanceTo( line, (10,5,0) ))
140 | See Also:
141 | LineClosestPoint
142 | LineIsFartherThan
143 | LineMinDistanceTo
144 | LinePlane
145 | LineTransform
146 | """
147 | line = rhutil.coerceline(line, True)
148 | test = rhutil.coerceline(point_or_line)
149 | if test is None: test = rhutil.coerce3dpoint(point_or_line, True)
150 | return line.MaximumDistanceTo(test)
151 |
152 |
153 | def LineMinDistanceTo(line, point_or_line):
154 | """Finds the shortest distance between a line as a finite chord, and a point
155 | or another line
156 | Parameters:
157 | line (line | [point, point]): List of 6 numbers, two Point3d, or Line.
158 | point_or_line (point|line): the test point or test line.
159 | Returns:
160 | number: A distance (D) such that if Q is any point on the line and P is any point on the other object, then D <= Rhino.Distance(Q, P).
161 | None: on error
162 | Example:
163 | import rhinoscriptsyntax as rs
164 | line = (0,0,0), (10,10,0)
165 | print(rs.LineMinDistanceTo(line, (10,5,0)))
166 | See Also:
167 | LineClosestPoint
168 | LineIsFartherThan
169 | LineMaxDistanceTo
170 | LinePlane
171 | LineTransform
172 | """
173 | line = rhutil.coerceline(line, True)
174 | test = rhutil.coerceline(point_or_line)
175 | if test is None: test = rhutil.coerce3dpoint(point_or_line, True)
176 | return line.MinimumDistanceTo(test)
177 |
178 |
179 | def LinePlane(line):
180 | """Returns a plane that contains the line. The origin of the plane is at the start of
181 | the line. If possible, a plane parallel to the world XY, YZ, or ZX plane is returned
182 | Parameters:
183 | line (line | [point, point]): List of 6 numbers, two Point3d, or Line.
184 | Returns:
185 | plane: the plane if successful
186 | None: if not successful
187 | Example:
188 | import rhinoscriptsyntax as rs
189 | lineFrom = (0,0,0)
190 | lineTo = (10,10,0)
191 | distance = rs.Distance(lineFrom, lineTo)
192 | plane = rs.LinePlane([lineFrom, lineTo])
193 | rs.AddPlaneSurface( plane, distance, distance )
194 | See Also:
195 | LineClosestPoint
196 | LineIsFartherThan
197 | LineMaxDistanceTo
198 | LineMinDistanceTo
199 | LineTransform
200 | """
201 | line = rhutil.coerceline(line, True)
202 | rc, plane = line.TryGetPlane()
203 | if not rc: return scriptcontext.errorhandler()
204 | return plane
205 |
206 |
207 | def LinePlaneIntersection(line, plane):
208 | """Calculates the intersection of a line and a plane.
209 | Parameters:
210 | line ([point, point]): Two 3D points identifying the starting and ending points of the line to intersect.
211 | plane (plane): The plane to intersect.
212 | Returns:
213 | point: The 3D point of intersection is successful.
214 | None: if not successful, or on error.
215 | Example:
216 | import rhinoscriptsyntax as rs
217 | plane = rs.WorldXYPlane()
218 | line = (2, 11, 13), (20, 4, -10)
219 | point = rs.LinePlaneIntersection(line, plane)
220 | if( point!=None ): rs.AddPoint(point)
221 | See Also:
222 | LineLineIntersection
223 | PlanePlaneIntersection
224 | """
225 | plane = rhutil.coerceplane(plane, True)
226 | line_points = rhutil.coerce3dpointlist(line, True)
227 | line = Rhino.Geometry.Line(line_points[0], line_points[1])
228 | rc, t = Rhino.Geometry.Intersect.Intersection.LinePlane(line, plane)
229 | if not rc: return scriptcontext.errorhandler()
230 | return line.PointAt(t)
231 |
232 |
233 | def LineSphereIntersection(line, sphere_center, sphere_radius):
234 | """Calculates the intersection of a line and a sphere
235 | Parameters:
236 | line (line | [point, point]): the line
237 | sphere_center (point): the center point of the sphere
238 | sphere_radius (number): the radius of the sphere
239 | Returns:
240 | list(point, ...): list of intersection points if successful, otherwise None
241 | Example:
242 | import rhinoscriptsyntax as rs
243 | radius = 10
244 | line = (-10,0,0), (10,0,10)
245 | points = rs.LineSphereIntersection(line, (0,0,0), radius)
246 | if points:
247 | for point in points: rs.AddPoint(point)
248 | See Also:
249 | LineCylinderIntersection
250 | LineLineIntersection
251 | LinePlaneIntersection
252 | """
253 | line = rhutil.coerceline(line, True)
254 | sphere_center = rhutil.coerce3dpoint(sphere_center, True)
255 | sphere = Rhino.Geometry.Sphere(sphere_center, sphere_radius)
256 | rc, pt1, pt2 = Rhino.Geometry.Intersect.Intersection.LineSphere(line, sphere)
257 | if rc==compat.ENUM_NONE(Rhino.Geometry.Intersect.LineSphereIntersection): return []
258 | if rc==Rhino.Geometry.Intersect.LineSphereIntersection.Single: return [pt1]
259 | return [pt1, pt2]
260 |
261 |
262 | def LineTransform(line, xform):
263 | """Transforms a line
264 | Parameters:
265 | line (guid): the line to transform
266 | xform (transform): the transformation to apply
267 | Returns:
268 | guid: transformed line
269 | Example:
270 | import rhinoscriptsyntax as rs
271 | line = (0,0,0), (10,10,0)
272 | rs.AddLine( line[0], line[1] )
273 | plane = rs.WorldXYPlane()
274 | xform = rs.XformRotation(30, plane.Zaxis, plane.Origin)
275 | line = rs.LineTransform(line, xform)
276 | rs.AddLine( line.From, line.To )
277 | See Also:
278 | LineClosestPoint
279 | LineIsFartherThan
280 | LineMaxDistanceTo
281 | LineMinDistanceTo
282 | LinePlane
283 | """
284 | line = rhutil.coerceline(line, True)
285 | xform = rhutil.coercexform(xform, True)
286 | success = line.Transform(xform)
287 | if not success: raise Exception("unable to transform line")
288 | return line
289 |
```
--------------------------------------------------------------------------------
/rhino_mcp_server/static/toolbar.py:
--------------------------------------------------------------------------------
```python
1 | import Rhino
2 |
3 |
4 | def CloseToolbarCollection(name, prompt=False):
5 | """Closes a currently open toolbar collection
6 | Parameters:
7 | name (str): name of a currently open toolbar collection
8 | prompt (bool, optional): if True, user will be prompted to save the collection file
9 | if it has been modified prior to closing
10 | Returns:
11 | bool: True or False indicating success or failure
12 | Example:
13 | import rhinoscriptsyntax as rs
14 | names = rs.ToolbarCollectionNames()
15 | if names:
16 | for name in names: rs.CloseToolbarCollection( name, True )
17 | See Also:
18 | IsToolbarCollection
19 | OpenToolbarCollection
20 | ToolbarCollectionCount
21 | ToolbarCollectionNames
22 | ToolbarCollectionPath
23 | """
24 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
25 | if tbfile: return tbfile.Close(prompt)
26 | return False
27 |
28 |
29 | def HideToolbar(name, toolbar_group):
30 | """Hides a previously visible toolbar group in an open toolbar collection
31 | Parameters:
32 | name (str): name of a currently open toolbar file
33 | toolbar_group (str): name of a toolbar group to hide
34 | Returns:
35 | bool: True or False indicating success or failure
36 | Example:
37 | import rhinoscriptsyntax as rs
38 | file = "C:\\SteveBaer\\AppData\\Roaming\\McNeel\\Rhinoceros\\5.0\\UI\\default.rui"
39 | name = rs.IsToolbarCollection(file)
40 | if names: rs.HideToolbar(name, "Layer")
41 | See Also:
42 | IsToolbar
43 | IsToolbarVisible
44 | ShowToolbar
45 | ToolbarCount
46 | ToolbarNames
47 | """
48 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
49 | if tbfile:
50 | group = tbfile.GetGroup(toolbar_group)
51 | if group:
52 | group.Visible = False
53 | return True
54 | return False
55 |
56 |
57 | def IsToolbar(name, toolbar, group=False):
58 | """Verifies a toolbar (or toolbar group) exists in an open collection file
59 | Parameters:
60 | name (str): name of a currently open toolbar file
61 | toolbar (str): name of a toolbar group
62 | group (bool, optional): if toolbar parameter is referring to a toolbar group
63 | Returns:
64 | bool: True or False indicating success or failure
65 | Example:
66 | import rhinoscriptsyntax as rs
67 | file = "C:\\SteveBaer\\AppData\\Roaming\\McNeel\\Rhinoceros\\5.0\\UI\\default.rui"
68 | name = rs.IsToolbarCollection(file)
69 | if name:
70 | if rs.IsToolbar(name, "Layer"):
71 | print("The collection contains the Layer toolbar.")
72 | else:
73 | print("The collection does not contain the Layer toolbar.")
74 | See Also:
75 | HideToolbar
76 | IsToolbarVisible
77 | ShowToolbar
78 | ToolbarCount
79 | ToolbarNames
80 | """
81 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
82 | if tbfile:
83 | if group: return tbfile.GetGroup(toolbar) != None
84 | return tbfile.GetToolbar(toolbar) != None
85 | return False
86 |
87 |
88 | def IsToolbarCollection(file):
89 | """Verifies that a toolbar collection is open
90 | Parameters:
91 | file (str): full path to a toolbar collection file
92 | Returns:
93 | str: Rhino-assigned name of the toolbar collection if successful
94 | None: if not successful
95 | Example:
96 | import rhinoscriptsyntax as rs
97 | file = "C:\\SteveBaer\\AppData\\Roaming\\McNeel\\Rhinoceros\\5.0\\UI\\default.rui"
98 | name = rs.IsToolbarCollection(file)
99 | if name: print("The default toolbar collection is loaded.")
100 | else: print("The default toolbar collection is not loaded.")
101 | See Also:
102 | CloseToolbarCollection
103 | OpenToolbarCollection
104 | ToolbarCollectionCount
105 | ToolbarCollectionNames
106 | ToolbarCollectionPath
107 | """
108 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByPath(file)
109 | if tbfile: return tbfile.Name
110 |
111 |
112 | def IsToolbarDocked(name, toolbar_group):
113 | """Verifies that a toolbar group in an open toolbar collection is visible
114 | Parameters:
115 | name (str): name of a currently open toolbar file
116 | toolbar_group (str): name of a toolbar group
117 | Returns:
118 | boolean: True or False indicating success or failure
119 | None: on error
120 | Example:
121 | import rhinoscriptsyntax as rs
122 | rc = rs.IsToolbarDocked("Default", "Main1")
123 | if rc==True:
124 | print("The Main1 toolbar is docked.")
125 | elif rc==False:
126 | print("The Main1 toolbar is not docked.")
127 | else:
128 | print("The Main1 toolbar is not visible.")
129 | See Also:
130 | IsToolbar
131 | IsToolbarVisible
132 | """
133 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
134 | if tbfile:
135 | group = tbfile.GetGroup(toolbar_group)
136 | if group: return group.IsDocked
137 |
138 |
139 | def IsToolbarVisible(name, toolbar_group):
140 | """Verifies that a toolbar group in an open toolbar collection is visible
141 | Parameters:
142 | name (str): name of a currently open toolbar file
143 | toolbar_group (str): name of a toolbar group
144 | Returns:
145 | bool:True or False indicating success or failure
146 | None: on error
147 | Example:
148 | import rhinoscriptsyntax as rs
149 | file = "C:\\SteveBaer\\AppData\\Roaming\\McNeel\\Rhinoceros\\5.0\\UI\\default.rui"
150 | name = rs.IsToolbarCollection(file)
151 | if name:
152 | if rs.IsToolbarVisible(name, "Layer"): print("The Layer toolbar is visible.")
153 | else: print("The Layer toolbar is not visible.")
154 | See Also:
155 | HideToolbar
156 | IsToolbar
157 | ShowToolbar
158 | ToolbarCount
159 | ToolbarNames
160 | """
161 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
162 | if tbfile:
163 | group = tbfile.GetGroup(toolbar_group)
164 | if group: return group.Visible
165 |
166 |
167 | def OpenToolbarCollection(file):
168 | """Opens a toolbar collection file
169 | Parameters:
170 | file (str): full path to the collection file
171 | Returns:
172 | str: Rhino-assigned name of the toolbar collection if successful
173 | None: if not successful
174 | Example:
175 | import rhinoscriptsyntax as rs
176 | file = "C:\\SteveBaer\\AppData\\Roaming\\McNeel\\Rhinoceros\\5.0\\UI\\default.rui"
177 | name = rs.IsToolbarCollection(file)
178 | if name is None: rs.OpenToolbarCollection(file)
179 | See Also:
180 | CloseToolbarCollection
181 | IsToolbarCollection
182 | ToolbarCollectionCount
183 | ToolbarCollectionNames
184 | ToolbarCollectionPath
185 | """
186 | tbfile = Rhino.RhinoApp.ToolbarFiles.Open(file)
187 | if tbfile: return tbfile.Name
188 |
189 |
190 | def SaveToolbarCollection(name):
191 | """Saves an open toolbar collection to disk
192 | Parameters:
193 | name (str): name of a currently open toolbar file
194 | Returns:
195 | bool: True or False indicating success or failure
196 | Example:
197 | import rhinoscriptsyntax as rs
198 | name = "Default"
199 | rs.SaveToolbarCollection(name)
200 | See Also:
201 | SaveToolbarCollectionAs
202 | """
203 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
204 | if tbfile: return tbfile.Save()
205 | return False
206 |
207 |
208 | def SaveToolbarCollectionAs(name, file):
209 | """Saves an open toolbar collection to a different disk file
210 | Parameters:
211 | name (str): name of a currently open toolbar file
212 | file (str): full path to file name to save to
213 | Returns:
214 | bool: True or False indicating success or failure
215 | Example:
216 | import rhinoscriptsyntax as rs
217 | name = "Default"
218 | file = "D:\\NewDefault.rui"
219 | rs.SaveToolbarCollectionAs(name,file)
220 | See Also:
221 | SaveToolbarCollection
222 | """
223 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
224 | if tbfile: return tbfile.SaveAs(file)
225 | return False
226 |
227 |
228 | def ShowToolbar(name, toolbar_group):
229 | """Shows a previously hidden toolbar group in an open toolbar collection
230 | Parameters:
231 | name (str): name of a currently open toolbar file
232 | toolbar_group (str): name of a toolbar group to show
233 | Returns:
234 | bool: True or False indicating success or failure
235 | Example:
236 | import rhinoscriptsyntax as rs
237 | file = "C:\\SteveBaer\\AppData\\Roaming\\McNeel\\Rhinoceros\\5.0\\UI\\default.rui"
238 | name = rs.IsToolbarCollection(file)
239 | if name: rs.ShowToolbar(name, "Layer")
240 | See Also:
241 | HideToolbar
242 | IsToolbar
243 | IsToolbarVisible
244 | ToolbarCount
245 | ToolbarNames
246 | """
247 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
248 | if tbfile:
249 | group = tbfile.GetGroup(toolbar_group)
250 | if group:
251 | group.Visible = True
252 | return True
253 | return False
254 |
255 |
256 | def ToolbarCollectionCount():
257 | """Returns number of currently open toolbar collections
258 | Returns:
259 | number: the number of currently open toolbar collections
260 | Example:
261 | import rhinoscriptsyntax as rs
262 | count = rs.ToolbarCollectionCount()
263 | print("There are {} toolbar(s) collections loaded".format(count))
264 | See Also:
265 | CloseToolbarCollection
266 | IsToolbarCollection
267 | OpenToolbarCollection
268 | ToolbarCollectionNames
269 | ToolbarCollectionPath
270 | """
271 | return Rhino.RhinoApp.ToolbarFiles.Count
272 |
273 |
274 | def ToolbarCollectionNames():
275 | """Returns names of all currently open toolbar collections
276 | Returns:
277 | list(str, ...): the names of all currently open toolbar collections
278 | Example:
279 | import rhinoscriptsyntax as rs
280 | names = rs.ToolbarCollectionNames()
281 | if names:
282 | for name in names: print(name)
283 | See Also:
284 | CloseToolbarCollection
285 | IsToolbarCollection
286 | OpenToolbarCollection
287 | ToolbarCollectionCount
288 | ToolbarCollectionPath
289 | """
290 | return [tbfile.Name for tbfile in Rhino.RhinoApp.ToolbarFiles]
291 |
292 |
293 | def ToolbarCollectionPath(name):
294 | """Returns full path to a currently open toolbar collection file
295 | Parameters:
296 | name (str): name of currently open toolbar collection
297 | Returns:
298 | str: full path on success
299 | None: on error
300 | Example:
301 | import rhinoscriptsyntax as rs
302 | names = rs.ToolbarCollectionNames()
303 | if names:
304 | for name in names: print(rs.ToolbarCollectionPath(name))
305 | See Also:
306 | CloseToolbarCollection
307 | IsToolbarCollection
308 | OpenToolbarCollection
309 | ToolbarCollectionCount
310 | ToolbarCollectionNames
311 | """
312 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
313 | if tbfile: return tbfile.Path
314 |
315 |
316 | def ToolbarCount(name, groups=False):
317 | """Returns the number of toolbars or groups in a currently open toolbar file
318 | Parameters:
319 | name (str): name of currently open toolbar collection
320 | groups (bool, optional): If true, return the number of toolbar groups in the file
321 | Returns:
322 | number: number of toolbars on success
323 | None: on error
324 | Example:
325 | import rhinoscriptsyntax as rs
326 | names = rs.ToolbarCollectionNames()
327 | if names:
328 | count = rs.ToolbarCount(names[0])
329 | print("The {} collection contains {} toolbars.".format(names[0], count))
330 | See Also:
331 | HideToolbar
332 | IsToolbar
333 | IsToolbarVisible
334 | ShowToolbar
335 | ToolbarNames
336 | """
337 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
338 | if tbfile:
339 | if groups: return tbfile.GroupCount
340 | return tbfile.ToolbarCount
341 |
342 |
343 | def ToolbarNames(name, groups=False):
344 | """Returns the names of all toolbars (or toolbar groups) found in a
345 | currently open toolbar file
346 | Parameters:
347 | name (str): name of currently open toolbar collection
348 | groups (bool, optional): If true, return the names of toolbar groups in the file
349 | Returns:
350 | list(str, ...): names of all toolbars (or toolbar groups) on success
351 | None: on error
352 | Example:
353 | import rhinoscriptsytax as rs
354 | names = rs.ToolbarCollectionNames()
355 | if names:
356 | toolbars = rs.ToolbarNames(names[0])
357 | if toolbars:
358 | for toolbar in toolbars: print(toolbar)
359 | See Also:
360 | HideToolbar
361 | IsToolbar
362 | IsToolbarVisible
363 | ShowToolbar
364 | ToolbarCount
365 | """
366 | tbfile = Rhino.RhinoApp.ToolbarFiles.FindByName(name, True)
367 | if tbfile:
368 | rc = []
369 | if groups:
370 | for i in range(tbfile.GroupCount): rc.append(tbfile.GetGroup(i).Name)
371 | else:
372 | for i in range(tbfile.ToolbarCount): rc.append(tbfile.GetToolbar(i).Name)
373 | return rc;
```