Scripting API
Staging
from ArtisanPlugin.Scripting import RenderApi as render, Transaction
Two helpers for arranging a piece before you photograph it: making the second half of a pair, and laying a piece flat so it sits on the ground plane instead of floating at whatever angle it was modelled. Unlike the Realtime Render viewer calls, both of these modify the Rhino document - they add or move real objects - so both need a valid licence and both belong inside a Transaction if you want one-step undo.
Neither follows the house 0-keeps-default convention: every number here is used literally, so distance = 0 really does mean zero millimetres of offset. Both take objectIds = None to mean “the current selection”, and both fail with “Nothing selected: select (or pass) at least one object.” when there is neither a list nor a selection, “No active document.” when no document is open, and “The given objects have no valid geometry.” when the ids resolve to nothing measurable.
Create a pair
with Transaction.Begin("Pair"):
new_ids = render.CreatePair(objectIds = None, distance = 20, angle = -20, mirror = False)
# -> [Guid, ...] : the ids of the copies
| Parameter | Default | Meaning |
|---|---|---|
objectIds | None | Ids to duplicate; None or empty = the current selection |
distance | 20 | Millimetres the copy is moved along the world X axis |
angle | -20 | Degrees the copy is rotated about the world Z axis |
mirror | False | Mirror the copy as well - for asymmetric designs such as earrings |
The ArtisanPair command, headless. The “pair” is the classic earring presentation: one piece as modelled and a second one beside it, turned slightly so the render reads as two objects rather than one duplicated twice. The originals are left exactly where they are - the new objects are copies, and the returned list holds the ids of those copies, in the same order as the input, skipping any object Rhino declined to duplicate.
The transform is built once for the whole selection, from the union bounding box of every input object: the copy is rotated angle degrees about the Z axis through that box’s centre, then translated distance millimetres along X. With mirror = True a mirror across the world YZ plane through the same centre is applied first, so the copy comes out as a true left/right counterpart before it is rotated and moved - which is what you want for an asymmetric design and what you do not want for a symmetric one, where it makes no visible difference.
Negative numbers are fine and are the normal way to place the copy on the other side: distance = -20 puts it left instead of right. The defaults match the command’s own (20 mm, -20 degrees, no mirror). The views are redrawn before the call returns.
Lie on the ground
with Transaction.Begin("Lie on ground"):
render.LieOnGround(objectIds = None, aroundX = True, keepOriginal = False)
# -> None
| Parameter | Default | Meaning |
|---|---|---|
objectIds | None | Ids to lay flat; None or empty = the current selection |
aroundX | True | Rotate about the world X axis; False rotates about world Y |
keepOriginal | False | False moves the objects themselves; True leaves the originals in place and flattens a copy |
The ArtisanLyingOnTheGround command, headless - the one you run before a flat-lay render. Returns nothing: with keepOriginal = False it transforms the given objects in place, and with keepOriginal = True it leaves them alone and adds a rotated duplicate instead (that is the command’s DeleteOriginal toggle, inverted). Either way the new ids are not reported back, so when you need them, note the document’s object list before and after.
It only rotates. All the objects share a single rotation about the world X (or Y) axis through the world origin - not through the piece’s own centre - and nothing is translated afterwards, so a piece modelled far from the origin swings a long way across the document, and a piece that ends up flat is not pushed down to touch Z = 0. Expect to move it yourself afterwards if the resting height matters.
The orientation is found by search, not by analysis: candidate angles from 0 to 180 degrees in 0.5-degree steps, each applied to a duplicate of every input geometry, keeping the angle whose combined bounding box has the smallest Z height - the flattest presentation. Two orientations are equally flat, 180 degrees apart, so the winner is compared against its complementary and the one whose average bounding-box bottom sits higher is chosen; that is the face-down orientation, the same one the command picks. Ties go to the lower angle, since only a strictly smaller height replaces the current best.
That is 360 candidate rotations, each duplicating and re-boxing every selected geometry. On a handful of breps it is instant; on dense meshes or a large selection it is the slow part of your script, and it scales with the number of objects rather than the number of steps you can change - the step size is fixed.
Composing the two
Pairing then flattening is the usual staging order, since CreatePair returns the ids you want to lay flat alongside the originals:
import Rhino
from ArtisanPlugin.Scripting import RenderApi as render, Transaction
doc = Rhino.RhinoDoc.ActiveDoc
originals = [o.Id for o in doc.Objects.GetSelectedObjects(False, False)]
with Transaction.Begin("Stage for render"):
copies = render.CreatePair(originals, distance = 22, angle = -15, mirror = True)
render.LieOnGround(list(originals) + list(copies))