yucad3d

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.

Python is NOT sandboxed. Unlike the Lua face, CPython runs with full process privileges — it can open files, sockets and subprocesses. The host therefore fails CLOSED: a script is executed only after a trust approver says yes, and if no approver is installed nothing loads at all. Treat a .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.

CallDoes
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

ContextCalls
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?

ChooseWhen
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