yucad3d

Build your first plugin

A complete, minimal plugin: one node type that stores a point and a radius, draws itself in plan and 3D, and a command that creates one. Every call below is in the host API reference.

1. The entry point

A plugin is a DLL exporting exactly one symbol. The host calls it once at startup and hands over the function table; you keep the pointer.

#include "yu3d_plugin.h"

static const Yu3dHostApi* H = 0;

YU3D_PLUGIN_EXPORT int yu3d_plugin_register(const Yu3dHostApi* host) {
    if (!host || host->abi_version != YU3D_PLUGIN_ABI_VERSION) return 0;
    H = host;
    register_my_type();
    return 1;   /* 0 = refuse to load */
}

Returning 0 is the honest failure: a plugin that cannot satisfy its contract must not half-register.

2. Declare the node type

The host owns the document tree; your plugin owns one opaque payload per node and is called back to (de)serialise, draw and destroy it.

typedef struct { double x, y, r; } MyCircle;

static void my_write(Yu3dWriter* w, const void* p) {
    const MyCircle* c = (const MyCircle*)p;
    H->w_f64(w, c->x); H->w_f64(w, c->y); H->w_f64(w, c->r);
}

static void* my_read(Yu3dReader* r) {
    MyCircle* c = (MyCircle*)calloc(1, sizeof *c);
    if (!H->r_f64(r, &c->x) || !H->r_f64(r, &c->y) || !H->r_f64(r, &c->r)) {
        free(c);
        return 0;           /* short read => fail the node, never guess */
    }
    return c;
}

static void my_destroy(void* p) { free(p); }

3. Draw it

Plan coordinates are world metres and colours are linear 0…1; the canvas owns zoom and styling. Emit pickable geometry and the host resolves clicks for you.

static void my_plan(Yu3dPlanCtx* ctx, const void* p) {
    const MyCircle* c = (const MyCircle*)p;
    for (int i = 0; i < 32; ++i) {
        double a0 = 6.28318530718 * i / 32.0, a1 = 6.28318530718 * (i + 1) / 32.0;
        H->plan_add_segment(ctx,
            c->x + c->r * cos(a0), c->y + c->r * sin(a0),
            c->x + c->r * cos(a1), c->y + c->r * sin(a1),
            0.9f, 0.5f, 0.1f, 1 /* pickable */);
    }
}

4. Register the type and a command

static void my_run(Yu3dDocument* doc, void* user) {
    MyCircle* c = (MyCircle*)calloc(1, sizeof *c);
    c->x = 0; c->y = 0; c->r = 5;
    H->doc_add_node(doc, "my.circle", c, "Circle");   /* one undoable edit */
}

static void register_my_type(void) {
    Yu3dNodeType t;
    memset(&t, 0, sizeof t);
    t.type_id     = "my.circle";
    t.write       = my_write;
    t.read        = my_read;
    t.destroy     = my_destroy;
    t.plan_render = my_plan;
    H->register_node_type(&t);
    H->register_command("my.circle.create", "Create Circle", "My Plugin",
                        my_run, 0);
}

5. What you get for free

BehaviourWho provides it
Undo / redo of your commandhost — doc_add_node is one edit
Save / load of your nodehost frames and versions the payload you write
Selection, picking, highlighthost, from the pickable geometry you emit
A document opened WITHOUT your pluginhost keeps the bytes verbatim and writes them back — no data loss

That last row is the point of the design. A colleague without your plugin can open, edit and save the file; your nodes survive untouched and reappear when the plugin is present again.