Scripting API
Queries
from ArtisanPlugin.Scripting import GemApi as gem
Before a script changes anything it usually has to find something: every stone in the piece, the ones the user just selected, the ones on the pavé layer, the ones that overlap. These are the methods that answer those questions.
They are all read-only. None of them touches the document, so none of them needs a Transaction, and none of them is gated on the licence — an unlicensed user can still count and inspect the stones in their own file. Every method that returns a list returns a real list, never None: an empty document, an unknown layer or a material nobody used simply gives you an empty list, so for g in gem.ByLayer(...) is always safe. The single exception is Find, which returns one gem or None.
Every gem comes back as an IGem handle — see the gem handle for what you can read and change through it.
All and Count
gems = gem.All() # -> list of IGem
n = gem.Count() # -> int
All() walks every object in the active document and returns a handle for each one that is an Artisan gemstone. Order follows the document’s own object order, which is roughly creation order, but do not rely on it as a stable sort — sort explicitly if order matters.
Count() is exactly len(All()); it does the same walk, so if you need both the number and the stones, call All() once and take its length rather than calling both.
Only Artisan gemstones are counted. A mesh imported from elsewhere that merely looks like a stone is invisible to these queries until it has been recovered into a real gem.
Find
g = gem.Find(id) # -> IGem, or None
Takes a System.Guid and returns a handle to that gem. It returns None in two cases: nothing in the document has that id, or the object with that id exists but is not a gem. That makes Find the natural bridge from any API that hands you raw ids — ToiEtMoiApi.Create, the gemset facades, Rhino’s own selection APIs — into the gem handle surface:
for id in ids:
g = gem.Find(id)
if g is not None:
g.SetMaterial("Ruby")
Note that Find does not itself throw for a stale id — it just gives you None. A handle you already hold is stricter: once its gem has been deleted, using it raises “Gem … no longer exists in the document.”
Selected
sel = gem.Selected() # -> list of IGem
The gems currently selected in the viewport, in document order. Non-gem objects in the selection are ignored, so a selection of a shank, two cutters and three stones gives you three handles. Nothing selected — or a selection with no stones in it — gives an empty list, which is the usual cue for a script to fall back to All():
targets = gem.Selected() or gem.All()
Selection is a viewport state, not a document mutation. Reading it costs nothing, and the matching Select(on) on the handle lets a script finish by highlighting what it produced.
ByLayer and ByMaterial
gem.ByLayer("Gems::Pave") # -> list of IGem
gem.ByMaterial("Ruby") # -> list of IGem
ByLayer matches on the layer’s full path, not its short name: a layer nested under Gems is "Gems::Pave", with Rhino’s :: separator. The lookup is exact — an unknown path, or a blank string, returns an empty list rather than throwing, so a script that queries a layer the document does not have simply does nothing.
ByMaterial filters All() by the gem’s compound name. The comparison is case-insensitive, so "ruby" and "RUBY" both work — but unlike Create, it performs no dash or space normalisation. The name must otherwise match the compound exactly as Materials() spells it, which matters for two-word compounds: pass the underscored form, not "rose gold". A blank string returns an empty list.
Collisions
for a, b in gem.Collisions():
a.Select(True)
b.Select(True)
Collisions() returns every pair of gems whose meshes intersect. A pair is a two-element list of handles, [gemA, gemB], and each unordered pair appears exactly once — you will never get both [a, b] and [b, a], and a gem is never paired with itself. A stone that overlaps three neighbours therefore appears in three separate pairs. An empty result means no two stones in the document touch.
The test is the same one the gems-collision command uses, run over every Artisan gemstone in the document. For each candidate pair it first compares the two meshes’ bounding boxes and skips the pair outright if those boxes do not overlap; only pairs that survive that cheap rejection go through Rhino’s fast mesh-mesh intersection, and the pair is reported if that intersection produces any polylines at all. So it is a genuine mesh intersection test, not a bounding-box approximation and not a distance threshold: two stones whose girdles are a hair apart do not collide, and two that share any volume do.
Gems whose mesh geometry is missing or invalid are skipped rather than reported. Because it is purely a read, Collisions() is safe to call in a loop while tuning a layout, and needs neither transaction nor licence.