Scripting API
Realtime Render viewer
from ArtisanPlugin.Scripting import RenderApi as render
Realtime Render is a separate window hosting a web-based viewer: Artisan exports the document to it as a GLB and the page renders it with its own camera, its own material catalogue and its own lighting. Driving it from a script is how you get a presentable image of the piece without setting up a raytraced render.
Two consequences run through this whole page, and ordering matters because of them. First, everything here talks to that window, never to the Rhino viewport - to stage the Rhino camera use rhinoscriptsyntax (rs.ViewCameraTarget, rs.RotateView, rs.ZoomExtents) or Views. Second, the window is not live-linked: it shows the model as of the last export, so UpdateRender is what pushes document changes across, and every other call on this page throws when the window is not open. The working sequence is always ShowRealtimeRender → UpdateRender → camera/materials → screenshot.
None of the calls on this page modifies the Rhino document, so none of them needs a Transaction. They change what the viewer window shows, nothing more.
Show the viewer
ok = render.ShowRealtimeRender() # -> True when the window opened or was activated
Opens the Realtime Render window, or activates it when it is already open - the ArtisanRealtimeRender command, which is hidden from the command list, so this is the only scriptable way in. Returns the command’s own success flag as a bool.
Requires a valid licence. The window carries its own local HTTP server, and the page needs a moment to load; the calls below fail cleanly if you get ahead of it, so retry rather than assume.
Push the document into the viewer
render.UpdateRender() # -> None
Re-exports the current document to the viewer - the ArtisanRenderUpdate command. The export runs in the background, so this returns as soon as the export is queued, not when the viewer has finished swapping the model in. Requires a valid licence.
Call it after any geometry or material change you want to see. The window is not live: without an UpdateRender the viewer keeps showing the model from the previous export.
If the viewer’s HTTP server is not running you get “The Realtime Render window is not open. Call RenderApi.ShowRealtimeRender() first.” - the underlying command only logged that condition, which a script would never see.
Camera presets
render.SetViewerCameraPreset("three-quarter") # -> None
Moves the viewer camera to a named preset and frames the model automatically. Valid ids, exactly as the viewer ships them:
front, back, left, right, top, bottom, three-quarter, three-quarter-left, detail, dramatic-low.
The name you pass is trimmed, lower-cased and has underscores turned into hyphens, so "Three_Quarter" and "three-quarter" are the same preset. Anything else throws “Unknown camera preset ‘<name>’. Valid: front, back, left, right, top, bottom, three-quarter, three-quarter-left, detail, dramatic-low.” - and that validation happens before the window is touched, so a typo fails the same way whether or not the viewer is open.
Explicit camera
from Rhino.Geometry import Point3d
render.SetViewerCamera(Point3d(40, -60, 30), Point3d(0, 0, 5)) # -> None
| Parameter | Default | Meaning |
|---|---|---|
position | required | Camera eye point, Point3d |
target | required | Point the camera looks at, Point3d |
Both points are in viewer/GLB coordinates, which are the same coordinates as the Rhino model, in millimetres. There is no field-of-view or roll argument: the viewer keeps its own lens settings.
Unlike the presets, this does not reframe - the view is exactly what those two points give you, so a target far from the geometry leaves the piece off-screen.
Zoom extents
render.ViewerZoomExtents() # -> None
Frames the whole model in the viewer, keeping the current direction. Useful after an UpdateRender that changed the size of the piece, or after a SetViewerCamera that overshot.
List the viewer’s parts
import json
raw = render.GetViewerItems() # -> str, never None
items = json.loads(json.loads(raw)) # -> [{"uuid": ..., "material": ..., ...}]
for item in items:
print(item["uuid"], item["material"])
Returns the viewer’s parts and their current materials as JSON: an array of objects carrying at least a uuid and a material. A “part” is one mesh node of the exported model - the granularity the viewer itself works at, which follows the export, not the Rhino object list. Call this first: SetViewerMaterial needs those uuids.
Two things about the string. It is JSON produced by JSON.stringify inside the page and then JSON-encoded again on the way out of WebView2, so in Python it takes two json.loads passes to reach the array - unlike ViewerScreenshot, which unwraps its outer layer for you. And it never returns None: when the page has not finished loading its viewer element you get the literal string "[]".
This is one of the two calls that waits for an answer. It gives the page 5 seconds and then throws “The viewer did not respond in time.”
Set a part’s material
render.SetViewerMaterial(part_uuid, "YELLOW_GOLD") # -> None
| Parameter | Default | Meaning |
|---|---|---|
partUuid | required | A uuid from GetViewerItems() |
materialName | required | A viewer catalogue name, e.g. YELLOW_GOLD, WHITE_GOLD, PLATINUM, DIAMOND, RUBY |
Viewer-only: the Rhino document is not modified - nothing is applied to the objects, no material table entry changes, and the next UpdateRender re-exports the document and puts the original materials back. To change the document’s materials use ApplyDesignMaterials / ApplyRenderMaterials instead.
The material name is trimmed and upper-cased for you, so "yellow_gold" works. An empty or whitespace partUuid throws “Part uuid is required (see GetViewerItems()).”; an empty materialName throws “Material name is required.” An unknown uuid or an unknown material name is not validated here - the call is fire-and-forget into the page, so a wrong name simply leaves the part as it was, with no error. The round trip:
import json
render.ShowRealtimeRender()
render.UpdateRender()
items = json.loads(json.loads(render.GetViewerItems()))
for item in items:
render.SetViewerMaterial(item["uuid"], "WHITE_GOLD")
Screenshot
path = render.ViewerScreenshot(r"C:\out\hero") # -> "C:\out\hero.png"
| Parameter | Default | Meaning |
|---|---|---|
path | required | Destination file; .png is appended when the extension is anything else |
Saves a PNG of the current viewer frame and returns the full path actually written, which is what you should use afterwards rather than the string you passed. Path handling, in order: the path is resolved to an absolute path (a relative one resolves against Rhino’s working directory, not your script’s folder), .png is appended unless the extension already is .png, and the containing directory is created if it does not exist. An empty or whitespace path throws “A destination file path is required.”
The frame captured is whatever the viewer is showing at that moment, at the window’s current size - so set the camera, and give the viewer a beat after UpdateRender, before capturing.
When the window is not open you get “The Realtime Render window is not open. Call RenderApi.ShowRealtimeRender() first.” When it is open but the page returns nothing - typically a viewer that has not finished loading - you get “The viewer did not return a screenshot.” This call waits up to 15 seconds for the page; past that it throws “The viewer did not respond in time.”
What throws when the window is closed
Every call on this page except SetViewerCameraPreset’s name check needs the Realtime Render window, and they all report it the same way: “The Realtime Render window is not open. Call RenderApi.ShowRealtimeRender() first.” UpdateRender checks the viewer’s HTTP server; the camera, material and screenshot calls check the window itself.
Worth keeping in mind when you script a batch: SetViewerCameraPreset, SetViewerCamera, ViewerZoomExtents and SetViewerMaterial do not wait for the page to answer. They return as soon as the instruction is queued, and anything that goes wrong inside the page is invisible to your script. Only GetViewerItems and ViewerScreenshot wait, and they are therefore the two calls that can time out.