Python scripting
An embedded CPython interpreter with a yu3d module
over the same seams the C ABI exposes: query and mutate the document,
register commands, and register data types that draw themselves in plan
and 3D. For automation you would otherwise write by hand.
.py in the scripts folder exactly like a
native plugin: run only what you trust.
Where scripts live
Every .py file in the application's scripts/
folder is offered at startup; each one is approved (or refused)
individually, then executed once in a shared global namespace. Registering
a command from that top-level code is what makes it appear in the UI.
Hello, command
import yu3d
def run(doc, args):
yu3d.show("%d node(s) in the document" % len(doc.nodes()))
yu3d.register_command("my.count", "Count nodes", run, group="Scripts")
The document
A Document is handed to your command; every mutation below is
one undoable edit — undo belongs to the host, never to the script.
| Call | Does |
|---|---|
nodes() | every node as (id, type, name) rows |
find(id) | one node by uuid |
selection() | what the user has selected |
add_group(name) · ensure_group(name, parent="") |
create a group; ensure_* is idempotent |
add_node(type_id, name, values=None) |
create a node of a registered data type |
rename(id, name) · remove(id) | edit the tree |
set_property(id, key, value, sub_id=-1) |
write one property; sub_id targets a sub-entity |
is_metric() · crs() · design_standard() · design_speed() |
project settings a script should respect rather than assume |
ground_elevation(x, y) | terrain height at a point |
align_evaluate(uuid, station) |
position, bearing and curvature at a station |
align_station_range(uuid) | start and end station |
A data type that draws itself
register_data_type gives a script the same deal a plugin gets:
the host persists the values, shows the properties, and calls back to draw.
import yu3d, math
def plan(ctx, values):
x, y, r = values["x"], values["y"], values["r"]
for i in range(32):
a0, a1 = 2 * math.pi * i / 32, 2 * math.pi * (i + 1) / 32
ctx.segment(x + r * math.cos(a0), y + r * math.sin(a0),
x + r * math.cos(a1), y + r * math.sin(a1))
ctx.text("r=%.1f" % r, x, y)
def scene(ctx, values):
x, y, r = values["x"], values["y"], values["r"]
ctx.add_line(x - r, y, 0.0, x + r, y, 0.0)
yu3d.register_data_type("py.circle",
properties={"x": 0.0, "y": 0.0, "r": 5.0},
plan=plan, scene=scene)
def run(doc, args):
doc.add_node("py.circle", "Circle", {"x": 10.0, "y": 4.0, "r": 7.5})
yu3d.register_command("py.circle.create", "Create circle", run)
Drawing contexts
| Context | Calls |
|---|---|
PlanCtx |
segment(x0,y0,x1,y1,r,g,b),
marker(x,y,…),
text(text,x,y,off_x,off_y,…),
triangle(x0,y0,x1,y1,x2,y2,…,a),
world_per_pixel(), real_width() |
SceneCtx |
add_line(x0,y0,z0,x1,y1,z1,…,width),
add_triangle(x0,y0,z0,…,x2,y2,z2,…) |
Coordinates are world metres and colours are linear 0…1, exactly as in the
C ABI. world_per_pixel() lets a script keep an annotation the
same size on screen at any zoom.
Command parameters
register_command(id, label, run, group="Scripts", page="", icon="",
params=None) — when params is given the host builds the
input form and hands the resolved values to run as
args, so a script never draws its own dialog.
Python or Lua?
| Choose | When |
|---|---|
| Lua | the script comes from elsewhere, or you want the sandbox's guarantees; automation over existing document operations |
| Python | you need the ecosystem (numpy, pandas, your own libraries) and you trust the code as much as a native plugin |
| C ABI plugin | new geometry kinds, heavy computation, or shipping to customers |