.. _plugin-sdk: .. index:: single: Plugins (Native SDK) Plugins (Native SDK) ==================== IQcad can be extended in two complementary ways. **Scripting** (Python or Pascal, chapters :ref:`python-scripting`, :ref:`python-reference` and :ref:`pascal-scripting`) runs interpreted code inside IQcad and is ideal for automation, batch work, and one-off tools. **Plugins** are *native compiled* libraries (``.dll`` / ``.so`` / ``.dylib``) that IQcad loads at startup; they run at full machine speed, can be distributed as binaries without exposing source, and add first-class commands, drawing operations, and event handlers exactly like built-in features. This chapter is the complete reference for the **native plugin SDK**: the design and ABI, the trust model and Plugin Manager, the manifest format, two step-by-step tutorials (Pascal and C), the bundled samples, and the full interface reference. .. contents:: :local: :depth: 2 .. note:: The plugin contract is shared with the scripting hosts — a plugin and a Pascal/Python script see the *same* drawing, layer, entity, surface and alignment model. If you only need automation, scripting is simpler; reach for a plugin when you need native speed, binary distribution, or you are shipping a tool to other users. .. index:: single: Plugins versus scripting Plugins versus scripting ------------------------ .. list-table:: :header-rows: 1 :widths: 28 36 36 * - Aspect - Scripting (Python / Pascal) - Native plugin * - Language - Python 3.12 or PascalScript - Any language that can produce a C-ABI shared library (Free Pascal, C, C++, Rust, …) * - Distribution - Source text (``.py`` / ``.pas``) - Compiled binary + a small JSON manifest * - Speed - Interpreted - Native machine code * - Loading - Run on demand from the editor or command line - Discovered and loaded at startup (once enabled) * - Trust - Sandboxed-ish; runs in-process - **Full process trust** — must be enabled with consent * - API - ``IScript*`` host objects - ``IIq*`` interfaces (a thin, frozen ABI overlay on the same engine) Both share the same underlying engine, so concepts such as *commands*, *events*, *layers*, and the *civil model* behave identically. .. index:: single: Architecture and design single: Architecture single: design Architecture and design ------------------------ .. index:: single: The contract The contract ~~~~~~~~~~~~ Everything a plugin can see or do is expressed through one Pascal unit and its hand-maintained C mirror: - ``cad/plugins/uIqSdk.pas`` — the **contract**: every ``IIq*`` interface, the result codes, the records, and the entry-point type. This file is the single source of truth. - ``cad/plugins/uIqSdkRtl.pas`` — small **runtime helpers** a Pascal plugin uses to build strings and enumerators (``IqStr``, ``IqRGB``, ``IqEnumStrings`` …). - ``cad/plugins/csdk/iqcad_sdk.h`` — the **C/C++ header**, a 1:1 mirror of the contract for plugins written in C or C++. A plugin never links against IQcad. It only ``uses uIqSdk`` (Pascal) or ``#include "iqcad_sdk.h"`` (C), exports one function, and talks to the host entirely through interface pointers handed to it at load time. .. index:: single: The vtable-interface ABI The vtable-interface ABI ~~~~~~~~~~~~~~~~~~~~~~~~~~ The SDK uses a **COM-style vtable ABI**: - Every object is an interface deriving from ``IInterface`` (``IUnknown``): the first three slots are ``QueryInterface``, ``AddRef``, ``Release``. Objects are **reference-counted**; the last ``Release`` frees them. - Every method uses the **C calling convention** (``cdecl``). This is the one convention shared by all three shipped 64-bit targets, so a Pascal host and a C plugin (or vice-versa) interoperate without glue. - **No managed types ever cross the boundary.** There are no Pascal ``string``/``array``/``Variant`` parameters in the contract. Variable length data is exposed as ref-counted interfaces (``IIqString``, ``IIqEnumerator``); fixed-size data is plain records (``TIqPoint3D``, ``TIqColor``, ``TIqVariant``). Text is always UTF-16 (``PWideChar`` / ``IIqString``). .. note:: There is **no Windows COM runtime** involved — no ``CoCreateInstance``, no registry, no apartments. "COM-style" here means only *the binary layout*: a vtable pointer followed by ``cdecl`` methods, with ``QueryInterface``/``AddRef``/``Release`` at the front. This works identically on Windows, Linux, and macOS, including Apple silicon. .. index:: single: The out-parameter convention The out-parameter convention ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ A function that *returns a value type* (a ``Double``, a ``TIqColor``, a ``LongInt``) returns it directly:: function Color: TIqColor; cdecl; function ElevationAt(const X, Y: Double; out Z: Double): TIqResult; cdecl; A function that *returns an interface or a string* returns it through an ``out`` parameter and yields a :ref:`result code ` instead:: function ActiveDrawing(out D: IIqDrawing): TIqResult; cdecl; function Name(out S: IIqString): TIqResult; cdecl; This is mandatory for ABI safety: returning a ref-counted interface *by function result* is not C-ABI-compatible across the Free Pascal / C boundary. **Always check both** the result code and the out-parameter — for example ``ActiveDrawing`` returns ``IQ_OK`` with ``D = nil`` when no drawing is open. .. index:: single: Frozen interfaces and versioning single: Frozen interfaces single: versioning Frozen interfaces and versioning ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Once an interface has shipped, **its layout never changes** — methods are never reordered, removed, or re-signed. The contract evolves only by: - **adding** methods to the *end* of an interface in a new minor version (existing plugins keep working — they simply do not call the new slots), or - **adding** an entirely new interface with a new GUID (``IIqDrawing2`` …) when a breaking change is unavoidable. Two constants in ``uIqSdk.pas`` track this: - ``IQSDK_API_MAJOR`` — bumped only on a breaking change. - ``IQSDK_API_MINOR`` — bumped on every additive change. A plugin declares the *minimum* minor it needs in its manifest (``apiVersion.min_minor``); the loader admits it only when ``host.major = manifest.major`` **and** ``host.minor >= manifest.min_minor`` (see :ref:`plugin-manifest`). The current contract is **API 1.6**. .. warning:: While the SDK is pre-1.0 it may still change *in place* between IQcad builds. A plugin compiled against an older contract can crash a newer IQcad. **Rebuild your plugins from the current SDK whenever you update IQcad**, until the contract is declared stable. .. index:: single: Threading Threading ~~~~~~~~~ The drawing model is **single-threaded**. All plugin entry points (command ``Invoke``, event callbacks) are called on IQcad's main thread, and you must only touch the drawing from that thread. If you spin up a worker thread, marshal back with:: Host.PostToMainThread(myRunnable); // myRunnable.Run executes on the main thread Calling drawing methods from another thread returns ``IQ_E_WRONGTHREAD`` or causes undefined behaviour. .. index:: single: Memory and lifetime single: Memory single: lifetime Memory and lifetime ~~~~~~~~~~~~~~~~~~~~ Objects are reference-counted. In Pascal this is automatic: assign an interface variable and the compiler emits ``AddRef``/``Release`` for you. In C you call ``lpVtbl->AddRef`` / ``lpVtbl->Release`` yourself (the samples use static singletons whose lifetime equals the library, so their refcount methods are no-ops). .. note:: **The plugin module is never unloaded while IQcad runs.** When you disable a plugin the loader deactivates it (it unregisters its commands and drops its references) but does **not** ``dlclose`` / ``FreeLibrary`` the binary. The host may still hold ref-counted objects whose *code lives in your library*; unmapping it would turn the next ``Release`` into a crash. The module stays mapped until IQcad exits. A disabled plugin is genuinely inert — its commands are gone — it simply remains in memory. .. index:: single: The plugin lifecycle The plugin lifecycle -------------------- A plugin binary exports exactly one function. Its lifetime is a fixed sequence driven by the loader: .. code-block:: text discover ──▶ load (dlopen) ──▶ IqcadPluginInit(host, out plugin) │ ▼ plugin.Describe(out info) ◀─ id must match manifest │ ▼ plugin.Activate(host) ◀─ register commands, subscribe events │ … commands run … │ ▼ plugin.Deactivate ◀─ unregister, release cookies │ ▼ references dropped (module stays mapped) 1. **Discover** — at startup the loader scans the plugins directory for sub-folders containing a ``plugin.json``, reads each manifest, and version-gates it. 2. **Load** — for each *enabled and consented* compatible plugin, the loader ``dlopen``\ s the binary and looks up the export ``IqcadPluginInit``. 3. **Init** — ``IqcadPluginInit(host, out plugin)`` creates your plugin object and returns it. The ``host`` you receive is your handle to everything. 4. **Describe** — the loader calls ``plugin.Describe`` and verifies the reported ``Id`` matches the manifest (a mismatch quarantines the plugin). 5. **Activate** — ``plugin.Activate(host)`` is where you register commands and subscribe to events. 6. **Deactivate** — on shutdown or when the user disables the plugin, ``plugin.Deactivate`` runs; unregister your commands and release any event-subscription cookies here. .. index:: single: Loader states Loader states ~~~~~~~~~~~~~~ Each discovered plugin carries a state visible in the Plugin Manager: .. list-table:: :header-rows: 1 :widths: 22 78 * - State - Meaning * - *Discovered* - Manifest read, not yet gated. * - *Eligible* - Compatible with this IQcad, not (yet) loaded. * - *Incompatible* - No binary for this platform, or API mismatch — never loaded. * - *Active* - Loaded and activated; its commands are live. * - *Failed* - Load or activation raised an error — quarantined, with the error recorded. .. index:: single: Trust and the Plugin Manager single: Trust single: Plugin Manager Trust and the Plugin Manager ---------------------------- A native plugin runs with the **full privileges of IQcad**: it can read and write files, open network connections, and a faulty plugin can crash the application. IQcad therefore treats plugins as untrusted until you say otherwise. **Plugins are disabled by default.** A freshly dropped-in plugin is discovered and listed but does *not* load until you enable it once and accept a trust prompt. Your choice is remembered, so the plugin autoloads on subsequent launches. .. index:: single: Opening the manager Opening the manager ~~~~~~~~~~~~~~~~~~~~ - **Command** — type :command:`PLUGINS` (alias ``PLUGINMANAGER``). - **Ribbon** — **Tools → Scripting → Plugins**. - **Menu** — **Tools → Plugin Manager…** .. _plugin-manager-ui: The dialog lists every discovered plugin with its *Enabled* flag, name, version, publisher and status, plus a details pane showing the id, the required API version, the install path, and any last error. .. index:: single: Enabling single: disabling single: reloading Enabling, disabling, reloading ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - **Enable** — select a plugin and click *Enable*. The first time, a trust dialog appears: .. code-block:: text Native plugins run with full process trust: they can read/write files, access the network, and a faulty plugin can crash IQcad. Only enable plugins you trust. Enable 'com.example.myfirst'? Click *Yes* to grant consent (remembered) and load the plugin. Its commands become available immediately and are announced on the command line. - **Disable** — deactivates the plugin (its commands disappear) and clears the enabled flag so it will not autoload next time. - **Reload** — re-scans the plugins directory and reconciles state; use it after installing or rebuilding a plugin without restarting IQcad. - **Open Folder** — opens the plugin's install directory (or the plugins root) in your file manager. .. index:: single: State persistence State persistence ~~~~~~~~~~~~~~~~~ Each plugin's *enabled* and *consented* flags are stored per-id in IQcad's ``settings.json`` and reapplied at startup. At launch IQcad *reconciles*: it loads only plugins that are eligible **and** enabled **and** consented. .. index:: single: Installing a plugin Installing a plugin ------------------- Plugins live under a per-user directory: .. list-table:: :header-rows: 1 :widths: 20 80 * - Platform - Plugins directory * - Linux / macOS - ``~/.config/IQcad/plugins/`` * - Windows - ``%APPDATA%\IQcad\plugins\`` Each plugin gets its **own sub-folder** containing the binary and the manifest: .. code-block:: text ~/.config/IQcad/plugins/ com.example.myfirst/ plugin.json libMyFirstPlugin.so (Linux) MyFirstPlugin.dll (Windows) libMyFirstPlugin.dylib (macOS) The sub-folder name is free-form — the loader reads the real id from ``plugin.json``. After copying the files, either restart IQcad or click **Reload** in the Plugin Manager, then enable the plugin. .. _plugin-manifest: .. index:: single: The manifest (plugin.json) The manifest (``plugin.json``) ------------------------------ Every plugin ships a ``plugin.json`` next to its binary. It tells the loader who the plugin is, which IQcad API it needs, and which binary file to load on each platform. .. list-table:: :header-rows: 1 :widths: 24 14 62 * - Field - Type - Meaning * - ``schemaVersion`` - int - Manifest format version — currently always ``1``. * - ``id`` - string - **Required.** Reverse-DNS unique id (e.g. ``com.example.myfirst``). Must match the id returned by ``Describe``. * - ``name`` - string - Display name shown in the Plugin Manager. * - ``version`` - string - The plugin's own version (informational, e.g. ``"1.0.0"``). * - ``apiVersion.major`` - int - Required SDK major version (must equal the host major). * - ``apiVersion.min_minor`` - int - Minimum SDK minor version the plugin needs. * - ``publisher`` - string - Optional publisher name. * - ``description`` - string - Optional one-line description. * - ``icon`` - string - Optional icon filename (reserved). * - ``entry`` - object - **Required.** Map of *platform key* → *library filename* for that platform. The recognised platform keys are ``x86_64-win64``, ``x86_64-linux`` and ``aarch64-darwin``. A complete manifest: .. code-block:: json { "schemaVersion": 1, "id": "com.example.myfirst", "name": "My First Plugin", "version": "1.0.0", "apiVersion": { "major": 1, "min_minor": 0 }, "entry": { "x86_64-win64": "MyFirstPlugin.dll", "x86_64-linux": "libMyFirstPlugin.so", "aarch64-darwin": "libMyFirstPlugin.dylib" }, "publisher": "Example", "description": "Adds the HELLO command." } .. warning:: The filename under ``entry`` must **exactly** match the binary you produce for that platform. The loader builds the path as *plugin-folder* + *entry filename* and loads precisely that file. A mismatch (e.g. manifest says ``libMyFirstPlugin.so`` but the build produced ``MyFirstPlugin``) is the single most common "my plugin won't load" mistake. .. _plugin-tutorial-pascal: .. index:: single: Tutorial 1 — your first plugin (Free Pascal) Tutorial 1 — your first plugin (Free Pascal) -------------------------------------------- This tutorial builds a complete, working plugin that adds a ``HELLO`` command. It is the smallest useful plugin and shows every required piece. .. index:: single: Prerequisites Prerequisites ~~~~~~~~~~~~~ - **Free Pascal / Lazarus** (the same toolchain IQcad is built with). - The IQcad/ICAD source tree, for the SDK units. Note the path to ``cad/plugins/`` — your project's unit search path must point there so ``uIqSdk`` and ``uIqSdkRtl`` resolve. .. index:: single: Step 1 — write the source Step 1 — write the source ~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create ``MyFirstPlugin.lpr``: .. code-block:: pascal library MyFirstPlugin; {$mode ObjFPC}{$H+} {$interfaces COM} // ref-counted COM-style interfaces uses Classes, SysUtils, uIqSdk, uIqSdkRtl; // the SDK contract + runtime helpers type { The command the user runs by typing HELLO. A command is just an object implementing IIqCommand. } TGreetCommand = class(TInterfacedObject, IIqCommand) private FHost: IIqHost; public constructor Create(const AHost: IIqHost); function Name(out S: IIqString): TIqResult; cdecl; function Aliases(out S: IIqString): TIqResult; cdecl; function Category(out S: IIqString): TIqResult; cdecl; function HelpText(out S: IIqString): TIqResult; cdecl; function Invoke(const Args: IIqEnumerator): TIqResult; cdecl; end; { The plugin object — created by IqcadPluginInit, owns the command. } TMyFirstPlugin = class(TInterfacedObject, IIqPlugin) private FHost: IIqHost; public function Describe(out Info: TIqPluginInfo): TIqResult; cdecl; function Activate(const Host: IIqHost): TIqResult; cdecl; function Deactivate: TIqResult; cdecl; end; { TGreetCommand } constructor TGreetCommand.Create(const AHost: IIqHost); begin inherited Create; FHost := AHost; end; function TGreetCommand.Name(out S: IIqString): TIqResult; cdecl; begin S := IqStr('HELLO'); Result := IQ_OK; end; function TGreetCommand.Aliases(out S: IIqString): TIqResult; cdecl; begin S := IqStr('HI'); Result := IQ_OK; end; // comma-separated for several function TGreetCommand.Category(out S: IIqString): TIqResult; cdecl; begin S := IqStr('Tutorial'); Result := IQ_OK; end; function TGreetCommand.HelpText(out S: IIqString): TIqResult; cdecl; begin S := IqStr('Print a friendly greeting'); Result := IQ_OK; end; function TGreetCommand.Invoke(const Args: IIqEnumerator): TIqResult; cdecl; begin // Args yields the command's arguments as IIqString items (none here). FHost.Log(illInfo, 'Hello from my first IQcad plugin!'); Result := IQ_OK; end; { TMyFirstPlugin } function TMyFirstPlugin.Describe(out Info: TIqPluginInfo): TIqResult; cdecl; begin Info.Id := IqStr('com.example.myfirst'); // MUST match plugin.json Info.Name := IqStr('My First Plugin'); Info.Version.Major := 1; Info.Version.Minor := 0; Result := IQ_OK; end; function TMyFirstPlugin.Activate(const Host: IIqHost): TIqResult; cdecl; var reg: IIqCommandRegistry; begin FHost := Host; if Host.Commands(reg) <> IQ_OK then Exit(IQ_E_FAIL); reg.Register(TGreetCommand.Create(Host)); Result := IQ_OK; end; function TMyFirstPlugin.Deactivate: TIqResult; cdecl; var reg: IIqCommandRegistry; begin if (FHost <> nil) and (FHost.Commands(reg) = IQ_OK) then reg.Unregister('HELLO'); FHost := nil; Result := IQ_OK; end; { The one exported symbol the loader looks for. } function IqcadPluginInit(const Host: IIqHost; out Plugin: IIqPlugin): TIqResult; cdecl; begin Plugin := TMyFirstPlugin.Create; Result := IQ_OK; end; exports IqcadPluginInit; begin end. That is the entire plugin. Note the four required ingredients: #. ``{$interfaces COM}`` so interfaces are reference-counted. #. One class implementing :ref:`IIqPlugin `. #. One or more classes implementing :ref:`IIqCommand `. #. The single exported ``IqcadPluginInit`` returning the plugin via its ``out`` parameter. .. index:: single: Step 2 — write the manifest Step 2 — write the manifest ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Create ``plugin.json`` beside the source (see :ref:`plugin-manifest` for all fields): .. code-block:: json { "schemaVersion": 1, "id": "com.example.myfirst", "name": "My First Plugin", "version": "1.0.0", "apiVersion": { "major": 1, "min_minor": 0 }, "entry": { "x86_64-linux": "libMyFirstPlugin.so", "x86_64-win64": "MyFirstPlugin.dll", "aarch64-darwin": "libMyFirstPlugin.dylib" } } .. index:: single: Step 3 — build Step 3 — build ~~~~~~~~~~~~~~ With a Lazarus project file (``.lpi``) whose *other unit files* path includes ``cad/plugins``, build the library: .. code-block:: bash lazbuild MyFirstPlugin.lpi or directly with the compiler, pointing ``-Fu`` at the SDK units: .. code-block:: bash fpc -Fu/path/to/ICAD/cad/plugins MyFirstPlugin.lpr On Linux this produces ``libMyFirstPlugin.so`` — the exact name the manifest's ``x86_64-linux`` entry expects. On Windows build a ``.dll``, on macOS a ``.dylib``, each matching its ``entry`` filename. .. index:: single: Step 4 — install and enable single: Step 4 — install single: enable Step 4 — install and enable ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Copy the binary and ``plugin.json`` into a sub-folder of the plugins directory: .. code-block:: bash mkdir -p ~/.config/IQcad/plugins/com.example.myfirst cp libMyFirstPlugin.so plugin.json ~/.config/IQcad/plugins/com.example.myfirst/ Start IQcad (or click **Reload** in the Plugin Manager), open the manager with :command:`PLUGINS`, select *My First Plugin*, click **Enable**, and accept the trust prompt. The command line confirms: .. code-block:: text Plugin command available: HELLO (aliases: HI) .. index:: single: Step 5 — run it Step 5 — run it ~~~~~~~~~~~~~~~~ Type ``HELLO`` (or the alias ``HI``) at the command line. You will see:: Hello from my first IQcad plugin! Type :command:`HELP` and scroll to the **Loaded plugin commands** section to see your command listed alongside any other plugins'. .. index:: single: Troubleshooting Troubleshooting ~~~~~~~~~~~~~~~ - **Plugin not listed** — the folder must contain a valid ``plugin.json`` with a non-empty ``id``. Click *Reload*. - **Status "Incompatible"** — no ``entry`` for your platform key, or the required ``apiVersion`` is newer than this IQcad. Check the details pane. - **Status "Failed"** — the binary could not be loaded, the ``IqcadPluginInit`` export is missing, or the ``Describe`` id does not match the manifest. The exact error is in the details pane and the :ref:`debug log `. - **Enabled but nothing happens** — confirm the command was announced on the command line; check ``HELP``. .. _plugin-tutorial-geometry: .. index:: single: Tutorial 2 — drawing geometry Tutorial 2 — drawing geometry ----------------------------- A more useful plugin draws into the active drawing. This ``DRAWDEMO`` command creates a layer and adds a few entities. Only the command's ``Invoke`` differs from Tutorial 1; everything else (the plugin class, the manifest, the build) is identical in shape. .. code-block:: pascal function TDrawDemoCommand.Invoke(const Args: IIqEnumerator): TIqResult; cdecl; var d: IIqDrawing; e: IIqEntity; a, b, c: TIqPoint3D; begin // 1. Get the active drawing (check BOTH the code and the out-param). if (FHost.ActiveDrawing(d) <> IQ_OK) or (d = nil) then begin FHost.Log(illInfo, 'DRAWDEMO: open a drawing first'); Exit(IQ_OK); end; // 2. Create a green layer and make it current. // TIqColor is packed RGB 0x00BBGGRR — build it with IqRGB, NOT an // ACI index. IqRGB(0,180,0) is green. d.CreateLayer('DEMO', IqRGB(0, 180, 0)); d.SetActiveLayer('DEMO'); // 3. Add entities. Points are TIqPoint3D records. a.X := 0; a.Y := 0; a.Z := 0; b.X := 10; b.Y := 10; b.Z := 0; d.AddLine(a, b, e); c.X := 5; c.Y := 5; c.Z := 0; d.AddCircle(c, 3, e); // 4. Read something back: the new circle's handle. if e <> nil then begin var h: IIqString; if e.Handle(h) = IQ_OK then FHost.Log(illInfo, PWideChar(WideString('DRAWDEMO: circle handle = ' + WideString(h.Chars)))); end; // 5. Repaint so the new geometry shows immediately. d.Invalidate; Result := IQ_OK; end; Key points: - ``IIqDrawing`` is reached through ``Host.ActiveDrawing``. It offers ``AddLine``, ``AddCircle``, ``AddArc``, ``AddRectangle``, ``AddText``, ``AddHatch`` and more (see :ref:`plugin-iface-drawing`). - **Colours are RGB, not ACI indices.** Use ``IqRGB(r, g, b)`` (Pascal) or ``IQ_RGB(r, g, b)`` (C). Passing a small integer like ``3`` yields ``RGB(3,0,0)`` — almost black — a classic mistake. - Call ``Invalidate`` (and optionally ``ZoomExtents``) so the view refreshes. Civil work follows the same pattern: ``AddSurveyPoint``, ``CreateSurface``, ``IIqSurface.ElevationAt``, alignment queries — see :ref:`plugin-iface-surface` and the ``civilui`` sample. .. _plugin-dialogs: .. index:: single: Parameter dialogs and message boxes single: Parameter dialogs single: message boxes Parameter dialogs and message boxes ----------------------------------- A plugin cannot create its own forms — IQcad is an LCL application and a second LCL instance inside the plugin would crash the process (see :ref:`plugin-sdk` intro). Instead the host builds the UI: you **describe** a dialog and IQcad creates, shows, and reads back every widget. .. index:: single: The rich message box The rich message box ~~~~~~~~~~~~~~~~~~~~~ ``IIqHostUI.MessageBox`` takes a caption, a button set, and an icon, and returns which button the user pressed: .. code-block:: pascal var ui: IIqHostUI; ans: TIqMsgResult; begin FHost.UI(ui); ui.MessageBox('Delete the selection?', 'Confirm', imbYesNo, imiQuestion, ans); if ans = imrYes then ...; // proceed end; Button sets are ``imbOK`` / ``imbOKCancel`` / ``imbYesNo`` / ``imbYesNoCancel``; icons ``imiNone`` / ``imiInfo`` / ``imiWarning`` / ``imiError`` / ``imiQuestion``; results ``imrOK`` / ``imrCancel`` / ``imrYes`` / ``imrNo``. A dismissed dialog (or no provider) returns ``imrCancel``. .. index:: single: A parameter dialog A parameter dialog ~~~~~~~~~~~~~~~~~~ Build a form, run it, read the values: .. code-block:: pascal var ui: IIqHostUI; dlg: IIqDialog; ok: LongBool; shape: LongInt; size: Double; layer: IIqString; col: TIqColor; filled: LongBool; begin FHost.UI(ui); ui.CreateDialog('Draw a shape', dlg); dlg.AddChoice('shape', 'Shape', 'Rectangle'#9'Circle', 0); // TAB separates options dlg.AddFloat ('size', 'Size (m)', 10, 0, 0, 2); // Min=Max=0 → unbounded, 2 decimals dlg.AddText ('layer', 'Layer name', 'SHAPES'); dlg.AddColor ('color', 'Color', IqRGB(0, 180, 0)); dlg.AddBool ('filled','Filled (hatch)?', False); dlg.Execute(ok); if not ok then Exit(IQ_OK); // user cancelled (or headless) dlg.GetChoice('shape', shape); dlg.GetFloat ('size', size); dlg.GetText ('layer', layer); dlg.GetColor ('color', col); dlg.GetBool ('filled', filled); // … create the layer and draw the rectangle/circle … end; Each field kind maps to the natural editor (text → edit, multiline → memo, int/float → spin, bool → checkbox, choice → dropdown, file/folder → a path field with *Browse*, color → a swatch + colour picker). See the full full ``IIqDialog`` method list in the :ref:`API reference `, and the complete worked example in the ``dialogdemo`` / ``cdialogdemo`` sample. .. _plugin-dialogs-scripting: .. index:: single: The same dialogs from scripting The same dialogs from scripting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The dialog engine is shared with the scripting hosts, so Python and Pascal scripts get the same parameter dialogs and rich message box. **Python** (see :ref:`python-reference`): .. code-block:: python dlg = iqcad.create_dialog("Draw a shape") dlg.add_choice("shape", "Shape", ["Rectangle", "Circle"], 0) dlg.add_float("size", "Size (m)", 10.0, decimals=2) dlg.add_color("color", "Color", 0x00B400) # 0x00BBGGRR if dlg.execute(): shape = dlg.get_choice("shape") size = dlg.get_float("size") answer = iqcad.message_box("Done", "DialogDemo", iqcad.MB_OK, iqcad.MB_ICON_INFO) **PascalScript** (see :ref:`pascal-scripting`): .. code-block:: pascal var dlg: TScriptDialog; begin dlg := CreateDialog('Draw a shape'); dlg.AddChoice('shape', 'Shape', 'Rectangle'#9'Circle', 0); dlg.AddFloat('size', 'Size (m)', 10, 0, 0, 2); if dlg.Execute then WriteLn(dlg.GetFloat('size')); end; .. _plugin-live-forms: .. index:: single: Live tool windows Live tool windows ----------------- A parameter dialog (:ref:`plugin-dialogs`) is **modal**: it blocks, the user fills it in, ``Execute`` returns. A **live tool window** is the opposite — a *non-modal*, host-owned window that stays open after the command returns. The plugin builds controls, **retains the handles**, mutates them at runtime, and reacts to the user through a callback object. This is how you build a side panel, a running log, or an interactive tool. As with dialogs, the **host owns every widget** — the plugin never touches LCL. ``IIqHostUI.CreateForm`` (API 1.4) returns an :ref:`IIqForm ` builder; each ``Add*`` creates a real widget *now* and hands back a retained :ref:`IIqControl ` handle. You pass an :ref:`IIqFormSink ` your plugin implements; the host calls it (on the main thread, boundary-guarded) when a button is clicked, a value changes, or the window closes. Lifetime is the careful part: - The host keeps the shown window in a registry, so it stays open even if your plugin releases its ``IIqForm`` handle. - When the window closes — the user clicks ``[X]``, you call ``Close``, your plugin is deactivated, or IQcad exits — the host fires ``OnFormClosed`` once, then marks the form **and all its control handles invalid**. - A call on a stale (post-close) handle safely returns ``IQ_E_FAIL`` — never a dangling dereference. - **Release your handles in ``Deactivate``** (and you may drop them in ``OnFormClosed``). Because plugin modules are never unloaded, even a leaked handle is safe — the wrapper is host-owned memory. .. index:: single: live window with a button that updates a list single: label A live window with a button that updates a list and a label ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This is the heart of the ``formdemo`` sample. The command builds a form with a status label, an integer spin, an *Add* button, and a read-only log list, then shows it. The sink's ``OnButtonClicked`` reads the spin value *live*, appends a line to the log, and updates the label — all while the window stays open: .. code-block:: pascal { The sink — implemented by the plugin; holds a plain ref to the form so its callbacks can drive the live controls. } type TFormDemoSink = class(TInterfacedObject, IIqFormSink) private FForm: IIqForm; // plain ref — the host registry owns the window public procedure SetForm(const AForm: IIqForm); procedure OnButtonClicked(const Key: PWideChar); cdecl; procedure OnValueChanged(const Key: PWideChar); cdecl; procedure OnFormClosed; cdecl; end; procedure TFormDemoSink.SetForm(const AForm: IIqForm); begin FForm := AForm; end; procedure TFormDemoSink.OnButtonClicked(const Key: PWideChar); cdecl; var c: IIqControl; n: LongInt; begin if (FForm = nil) or (WideString(Key) <> 'add') then Exit; // Read the spin value LIVE through its handle. n := 0; if (FForm.FindControl('n', c) = IQ_OK) and (c <> nil) then c.GetInt(n); // Append a line to the log list. if (FForm.FindControl('log', c) = IQ_OK) and (c <> nil) then c.AppendLine(PWideChar(WideString(WideFormat('Added: %d', [n])))); // Update the status label. if (FForm.FindControl('status', c) = IQ_OK) and (c <> nil) then c.SetText(PWideChar(WideString(WideFormat('Last added: %d', [n])))); end; procedure TFormDemoSink.OnValueChanged(const Key: PWideChar); cdecl; begin end; procedure TFormDemoSink.OnFormClosed; cdecl; begin FForm := nil; end; // the host already freed the window { The command — builds and shows the form. } function TFormDemoCommand.Invoke(const Args: IIqEnumerator): TIqResult; cdecl; var ui: IIqHostUI; frm: IIqForm; c: IIqControl; sink: TFormDemoSink; begin ui := nil; FHost.UI(ui); if ui = nil then Exit(IQ_OK); // headless — nothing to show sink := TFormDemoSink.Create; FSink := sink; // keep the sink alive frm := nil; if (ui.CreateForm('Form demo', FSink, frm) <> IQ_OK) or (frm = nil) then begin FSink := nil; Exit(IQ_OK); end; // no form provider sink.SetForm(frm); FForm := frm; // retain — release in Deactivate frm.AddLabel ('status', 'Click Add to append a value', c); frm.AddInt ('n', 'Value', 1, 0, 1000, c); frm.AddButton('add', 'Add', c); frm.AddList ('log', 'Log', c); frm.Show; // non-modal — it outlives Invoke Result := IQ_OK; end; Release the retained ``FForm`` and ``FSink`` handles in ``Deactivate`` (the already-shown window stays alive in the host registry regardless). The complete, compilable version — including the plugin object and the ``min_minor: 4`` manifest — is the ``formdemo`` / ``cformdemo`` sample. .. index:: single: The same live forms from scripting The same live forms from scripting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Live forms are part of the shared UI engine, so Python and Pascal scripts open them too. **Python** (see :ref:`python-reference`) — ``iqcad.create_form(title, handler)`` returns a live form; the optional ``handler`` object's ``on_button_clicked(key)`` / ``on_value_changed(key)`` / ``on_form_closed()`` methods are called by the host: .. code-block:: python form = iqcad.create_form("Form demo", handler) form.add_label("status", "Click Add to append a value") form.add_int("n", "Value", 1, 0, 1000) form.add_button("add", "Add") form.add_list("log", "Log") form.show() **PascalScript** (see :ref:`pascal-scripting`) — ``CreateForm(title)`` returns a ``TScriptForm``; assign script procedure names to its event handlers: .. code-block:: pascal frm := CreateForm('Form demo'); frm.AddInt('n', 'Value', 1, 0, 1000); frm.AddButton('add', 'Add'); frm.AddList('log', 'Log'); frm.OnButtonClicked := 'HandleClick'; { name of a script procedure } frm.Show; .. _plugin-docking: .. index:: single: Docking a panel Docking a panel ~~~~~~~~~~~~~~~ A live tool window normally floats. ``IIqHostUI.CreatePanel`` (API 1.5) is the **docked** alternative: it is *identical* to ``CreateForm`` — same arguments, same returned :ref:`IIqForm ` builder, same :ref:`IIqControl ` handles and the same :ref:`IIqFormSink ` callbacks — but instead of a floating window the form's widgets are hosted as a **tab in a docked strip on the right of the main window**, between the canvas and the Object Inspector. The strip appears when the first panel docks and hides again when its last tab closes. ``Show`` adds (or activates) the tab; ``Close`` — or the user closing the tab — removes it and fires ``OnFormClosed`` exactly once. Floating versus docked is purely the plugin's choice: call ``CreateForm`` for a floating window, ``CreatePanel`` for a docked tab; nothing else in your code changes. .. code-block:: pascal { Exactly the live-form example, but docked. Build the same sink and controls. } frm := nil; if (ui.CreatePanel('Form demo', FSink, frm) <> IQ_OK) or (frm = nil) then begin FSink := nil; Exit(IQ_OK); end; // no dock provider (headless) sink.SetForm(frm); FForm := frm; // retain — release in Deactivate { … AddLabel / AddInt / AddButton / AddList exactly as for CreateForm … } frm.Show; // appears as a tab in the right-side dock strip The same docked panel is available from scripting: Python (``iqcad.create_panel(title, handler)`` — see :ref:`python-reference`) and PascalScript (``CreatePanel(title)`` — see :ref:`pascal-scripting`), each mirroring its floating ``create_form`` / ``CreateForm`` counterpart. .. _plugin-ribbon-menu: .. index:: single: Ribbon & menu contributions single: Ribbon single: menu contributions Ribbon & menu contributions --------------------------- Commands typed at the command line are powerful but not *discoverable*. To surface a plugin's commands in the GUI, a plugin **adds ribbon buttons and menu items that run a registered command**. Two ``IIqHostUI`` methods (API 1.6) do this: .. code-block:: pascal function AddRibbonButton(const Group, CommandName, Caption, Hint, IconFile: PWideChar; Large: LongBool): TIqResult; cdecl; // (1.6) function AddMenuItem(const Submenu, CommandName, Caption: PWideChar): TIqResult; cdecl; // (1.6) Every contribution lands in a **dedicated location** — a single **Plugins** ribbon tab and a single **Plugins** top-level menu. Each plugin gets its own **pane** (on the ribbon) and **submenu** (in the menu), named by the ``Group`` / ``Submenu`` argument; pass an empty string to use the plugin's display name. There is no contributing into existing host tabs or menus, so plugins never collide. - **Command-bound.** ``CommandName`` is a command you registered through the :ref:`command registry `; clicking the button or menu item runs it through the host's normal command path. There is no separate click callback. The contribution is created even if the command is not yet registered (it resolves by name at click time); clicking an unknown command is a harmless no-op logged to the :ref:`debug log `. - **Icons** are a PNG **relative to the plugin folder** (the same folder as ``plugin.json``); the host loads and scales it into the ribbon image lists. Omit it (empty ``IconFile``) for a generic plugin icon; ``AddMenuItem`` is text-only and ignores icons. ``Large`` selects a large versus small ribbon button. - **Auto-cleanup.** Contributions are tagged to the plugin that added them and are removed automatically when the plugin is disabled, reloaded, or IQcad exits. The **Plugins** tab and menu hide again once no contributions remain. ``AddRibbonButton`` / ``AddMenuItem`` are normally called in ``Activate``, *after* registering the command: .. code-block:: pascal function TMyPlugin.Activate(const Host: IIqHost): TIqResult; cdecl; var reg: IIqCommandRegistry; ui: IIqHostUI; begin FHost := Host; // 1. Register the command (must succeed). if Host.Commands(reg) <> IQ_OK then Exit(IQ_E_FAIL); reg.Register(TMyCommand.Create(Host)); // command name 'MYCMD' // 2. Make it discoverable in the GUI (tolerate a headless host). ui := nil; Host.UI(ui); if ui <> nil then begin ui.AddRibbonButton('Demo', 'MYCMD', 'Hello', 'Say hello', 'icon.png', True); ui.AddMenuItem('Demo', 'MYCMD', 'Hello'); end; Result := IQ_OK; end; A headless host (or a build with no contributions provider) returns ``IQ_E_FAIL`` from these methods; tolerate it — the command itself is still registered and usable from the command line. .. index:: single: The same contributions from scripting The same contributions from scripting ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The contribution engine is shared with the scripting hosts, so Python and Pascal scripts add ribbon buttons and menu items too. Scripts have no install folder or id, so their contributions appear under a single **Scripts** group / submenu, and ``icon`` is an **absolute** path. **Python** (see :ref:`python-reference`) — ``iqcad.add_ribbon_button(group, command, caption, hint='', icon='', large=True)`` and ``iqcad.add_menu_item(submenu, command, caption)``. **PascalScript** (see :ref:`pascal-scripting`) — ``AddRibbonButton(group, command, caption, hint, iconfile, large)`` and ``AddMenuItem(submenu, command, caption)``. .. _plugin-tutorial-c: .. index:: single: Writing a plugin in C or C++ Writing a plugin in C or C++ ---------------------------- Because the ABI is plain C vtables, you can write a plugin in C/C++ (or any language with C FFI). Include ``iqcad_sdk.h`` and implement the same interfaces as vtable structs. Where Pascal gives you language interfaces and ``IqStr``, in C you build the vtable by hand and supply UTF-16 strings yourself. A minimal command plugin: .. code-block:: c #include "iqcad_sdk.h" #include /* ---- a reusable static UTF-16 string object ------------------------ */ typedef struct { const IIqStringVtbl* lpVtbl; const iq_wchar* s; iq_int n; } CStr; static iq_result IQ_CALL str_QI(void* self, const IqGUID* iid, void** o) { if (!memcmp(iid, &IID_IIqString, sizeof(IqGUID))) { *o = self; return IQ_OK; } *o = NULL; return IQ_E_NOTFOUND; } static iq_uint IQ_CALL str_AddRef(void* self) { (void)self; return 1; } static iq_uint IQ_CALL str_Release(void* self) { (void)self; return 1; } static iq_int IQ_CALL str_Length(void* self){ return ((CStr*)self)->n; } static const iq_wchar* IQ_CALL str_Chars(void* self){ return ((CStr*)self)->s; } static const IIqStringVtbl g_strVtbl = { str_QI, str_AddRef, str_Release, str_Length, str_Chars }; #define CSTR(VAR, LIT, LEN) static CStr VAR = { &g_strVtbl, u##LIT, LEN } /* ---- the command --------------------------------------------------- */ CSTR(g_name, "CHELLO", 6); CSTR(g_aliases, "CHI", 3); CSTR(g_cat, "Tutorial", 8); CSTR(g_help, "C greeting", 10); typedef struct { const IIqCommandVtbl* lpVtbl; IIqHost* host; } CmdObj; static iq_result IQ_CALL cmd_QI(void* self, const IqGUID* iid, void** o) { if (!memcmp(iid, &IID_IIqCommand, sizeof(IqGUID))) { *o = self; return IQ_OK; } *o = NULL; return IQ_E_NOTFOUND; } static iq_uint IQ_CALL cmd_AddRef(void* self) { (void)self; return 1; } static iq_uint IQ_CALL cmd_Release(void* self) { (void)self; return 1; } static iq_result IQ_CALL cmd_Name (void* s, IIqString** o){ *o=(IIqString*)&g_name; return IQ_OK; } static iq_result IQ_CALL cmd_Aliases(void* s, IIqString** o){ *o=(IIqString*)&g_aliases; return IQ_OK; } static iq_result IQ_CALL cmd_Category(void* s,IIqString** o){ *o=(IIqString*)&g_cat; return IQ_OK; } static iq_result IQ_CALL cmd_Help (void* s, IIqString** o){ *o=(IIqString*)&g_help; return IQ_OK; } static iq_result IQ_CALL cmd_Invoke (void* self, IIqEnumerator* args) { CmdObj* c = (CmdObj*)self; (void)args; c->host->lpVtbl->Log(c->host, IQ_LOG_INFO, u"Hello from a C plugin!"); return IQ_OK; } static const IIqCommandVtbl g_cmdVtbl = { cmd_QI, cmd_AddRef, cmd_Release, cmd_Name, cmd_Aliases, cmd_Category, cmd_Help, cmd_Invoke }; static CmdObj g_cmd = { &g_cmdVtbl, NULL }; /* ---- the plugin object (Describe/Activate/Deactivate) -------------- */ /* … same pattern: a struct with const IIqPluginVtbl*, fill the slots, Activate() calls host->Commands(host,®); reg->Register(reg, &g_cmd) … */ IQ_EXPORT iq_result IqcadPluginInit(IIqHost* host, IIqPlugin** out_plugin) { g_cmd.host = host; *out_plugin = /* address of your IIqPlugin singleton */; return IQ_OK; } Build it as a shared library and install exactly as a Pascal plugin: .. code-block:: bash gcc -shared -fPIC -std=c11 -I/path/to/ICAD/cad/plugins/csdk \ -o libCHello.so chello.c See the ``chello`` sample for the complete, compilable version including the plugin object and ``QueryInterface`` details. .. note:: In C you call every method as ``obj->lpVtbl->Method(obj, args…)`` — the object pointer is always the implicit first argument. Strings are ``iq_wchar*`` (UTF-16); use ``u"…"`` literals. Colours use the ``IQ_RGB(r,g,b)`` macro. .. _plugin-samples: .. index:: single: The bundled samples The bundled samples ------------------- The SDK ships eighteen worked samples under ``cad/plugins/samples/`` — nine in Free Pascal, each with a C twin. They form a learning path from the trivial to the civil: .. list-table:: :header-rows: 1 :widths: 8 18 16 18 40 * - # - Pascal - C twin - Command (alias) - Demonstrates * - 1 - ``hello`` - ``chello`` - ``HELLO`` (``HI``) / ``CHELLO`` (``CHI``) - The minimal skeleton: entry point, ``IIqPlugin``, one command, ``Host.Log``. The Pascal version also subscribes to an event. * - 2 - ``geom`` - ``cgeom`` - ``DRAWDEMO`` (``DD``) - Drawing geometry: ``CreateLayer`` + ``SetActiveLayer``, ``AddLine`` / ``AddCircle`` / ``AddArc`` / ``AddRectangle`` / ``AddHatch``, reading entity handles, ``Invalidate``. *Needs API ≥ 1.2.* * - 3 - ``entsel`` - ``centsel`` - ``RECOLOR`` (``RC``) - Selection and entities: iterate ``Selection`` / ``Entities`` with an ``IIqEnumerator``, ``QueryInterface`` each to ``IIqEntity``, ``SetColor(IqRGB(…))``. * - 4 - ``events`` - ``cevents`` - *(none — a listener)* - Events: ``Subscribe`` an ``IIqEventSink``, implement all nine callbacks, release the cookie on ``Deactivate``. *Needs API ≥ 1.2.* * - 5 - ``civilui`` - ``ccivilui`` - ``SURVEYDEMO`` (``SD``) - Civil + UI: ``IIqHostUI.PromptString`` / ``MessageBox``, ``AddSurveyPoint``, ``CreateSurface`` (TIN), ``ElevationAt``. * - 6 - ``dialogdemo`` - ``cdialogdemo`` - ``DIALOGDEMO`` (``DLG``) - Parameter dialog: ``CreateDialog`` + ``Add*`` (choice/float/text/ color/bool) + ``Execute`` + ``Get*``, then draws the shape and shows a rich ``MessageBox``. *Needs API ≥ 1.3.* * - 7 - ``formdemo`` - ``cformdemo`` - ``FORMDEMO`` (``FORM``) - Live tool window: ``CreateForm`` + controls + an ``IIqFormSink`` button callback updating a list/label live. *Needs API ≥ 1.4.* * - 8 - ``dockdemo`` - ``cdockdemo`` - ``DOCKDEMO`` (``DOCK``) - Docked panel: same as ``formdemo`` but via ``CreatePanel`` — lives as a tab in the right-side dock strip. *Needs API ≥ 1.5.* * - 9 - ``ribbondemo`` - ``cribbondemo`` - ``RIBBONDEMO`` (``RBN``) - Adds a ribbon button + menu item (with a PNG icon) that run a command: ``AddRibbonButton`` / ``AddMenuItem`` into the **Plugins** tab/menu. *Needs API ≥ 1.6.* Recommended order: **hello → geom → entsel → events → civilui → dialogdemo → formdemo → dockdemo → ribbondemo**, with the C twin alongside each. ``hello`` / ``chello`` are the smallest complete examples; ``samples/README.md`` has build and install notes per sample. .. _plugin-api-reference: .. index:: single: API reference API reference ------------- This section lists the public contract. Signatures are Pascal (``uIqSdk.pas``); the C header ``iqcad_sdk.h`` mirrors each as a vtable slot with an explicit ``self`` first argument. .. _plugin-result-codes: .. index:: single: Result codes (TIqResult) Result codes (``TIqResult``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. list-table:: :header-rows: 1 :widths: 30 12 58 * - Constant - Value - Meaning * - ``IQ_OK`` - 0 - Success. * - ``IQ_E_FAIL`` - -1 - Generic failure. * - ``IQ_E_NOTIMPL`` - -2 - Method not implemented for this object. * - ``IQ_E_INVALIDARG`` - -3 - A bad argument was supplied. * - ``IQ_E_NOTFOUND`` - -4 - Requested item does not exist. * - ``IQ_E_OUTOFBOUNDS`` - -5 - Index out of range. * - ``IQ_E_WRONGTHREAD`` - -6 - Called from a thread other than the main thread. .. index:: single: Core types Core types ~~~~~~~~~~ .. code-block:: pascal TIqVersion = record Major, Minor: Word; end; TIqPoint3D = record X, Y, Z: Double; end; PIqPoint3D = ^TIqPoint3D; TIqColor = LongInt; // packed RGB 0x00BBGGRR TIqLogLevel = (illDebug, illInfo, illWarning, illError); TIqVariantKind = (ivkNull, ivkInt, ivkFloat, ivkBool, ivkString); TIqVariant = record Kind: TIqVariantKind; Num: Double; Str: IIqString; end; TIqPluginInfo = record Id: IIqString; Name: IIqString; Version: TIqVersion; end; // message-box enums (API 1.3) TIqMsgButtons = (imbOK, imbOKCancel, imbYesNo, imbYesNoCancel); TIqMsgIcon = (imiNone, imiInfo, imiWarning, imiError, imiQuestion); TIqMsgResult = (imrOK, imrCancel, imrYes, imrNo); **Colour** — ``TIqColor`` is a packed RGB value, ``0x00BBGGRR`` (red in the low byte, like a Win32 ``COLORREF``). It is **not** an ACI palette index. Build it with ``IqRGB(r, g, b)`` / ``IQ_RGB(r, g, b)``. **Variant** — ``TIqVariant`` carries a typed scalar for entity properties. ``Num`` holds the integer, float, or boolean (0/1) value; ``Str`` is valid only when ``Kind = ivkString``. It is always passed by ``const``/``out`` reference, never by value. .. index:: single: Runtime helpers (uIqSdkRtl) Runtime helpers (``uIqSdkRtl``) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: pascal function IqStr(const S: WideString): IIqString; function IqRGB(R, G, B: Byte): TIqColor; function IqEnumStrings(const Items: array of WideString): IIqEnumerator; function IqEnumInterfaces(const Items: array of IInterface): IIqEnumerator; function IqProfilePoint(AStation, AZ: Double): IIqProfilePoint; These wrap the common cases (a string, a colour, an enumerator). To expose your own variable-length data, derive ``TInterfacedObject`` and implement ``IIqString`` or ``IIqEnumerator`` directly. .. index:: single: Entry point Entry point ~~~~~~~~~~~ .. code-block:: pascal // exported under the name 'IqcadPluginInit' function IqcadPluginInit(const Host: IIqHost; out Plugin: IIqPlugin): TIqResult; cdecl; C: ``iq_result IqcadPluginInit(IIqHost* host, IIqPlugin** out_plugin);`` .. _plugin-iface-plugin: .. index:: single: IIqPlugin ``IIqPlugin`` ~~~~~~~~~~~~~ The object your ``IqcadPluginInit`` returns. .. code-block:: pascal function Describe(out Info: TIqPluginInfo): TIqResult; cdecl; // id MUST match manifest function Activate(const Host: IIqHost): TIqResult; cdecl; // register commands/events here function Deactivate: TIqResult; cdecl; // unregister/unsubscribe here .. index:: single: IIqHost ``IIqHost`` ~~~~~~~~~~~ Your handle to IQcad, passed to ``IqcadPluginInit`` and ``Activate``. .. code-block:: pascal function ApiVersion: TIqVersion; cdecl; function ActiveDrawing(out D: IIqDrawing): TIqResult; cdecl; // D may be nil (no drawing) function Commands(out R: IIqCommandRegistry): TIqResult; cdecl; function Events(out S: IIqEventSource): TIqResult; cdecl; function UI(out U: IIqHostUI): TIqResult; cdecl; function Log(Level: TIqLogLevel; const Msg: PWideChar): TIqResult; cdecl; function PostToMainThread(const Callback: IIqRunnable): TIqResult; cdecl; .. index:: single: IIqHostUI ``IIqHostUI`` ~~~~~~~~~~~~~ Prompts, message boxes, and parameter dialogs. All UI is built and owned by the host (the plugin never touches LCL — see :ref:`plugin-dialogs`). .. code-block:: pascal function MessageBox(const Text, Caption: PWideChar; Buttons: TIqMsgButtons; Icon: TIqMsgIcon; out Answer: TIqMsgResult): TIqResult; cdecl; function PromptString(const Prompt, Default: PWideChar; out Value: IIqString): TIqResult; cdecl; function PickFile(const Title, Filter: PWideChar; SaveDlg: LongBool; out Path: IIqString): TIqResult; cdecl; function CreateDialog(const Title: PWideChar; out Dlg: IIqDialog): TIqResult; cdecl; // (1.3) function CreateForm(const Title: PWideChar; const Sink: IIqFormSink; out Form: IIqForm): TIqResult; cdecl; // (1.4) function CreatePanel(const Title: PWideChar; const Sink: IIqFormSink; out Form: IIqForm): TIqResult; cdecl; // (1.5) function AddRibbonButton(const Group, CommandName, Caption, Hint, IconFile: PWideChar; Large: LongBool): TIqResult; cdecl; // (1.6) function AddMenuItem(const Submenu, CommandName, Caption: PWideChar): TIqResult; cdecl; // (1.6) .. index:: single: IIqDialog ``IIqDialog`` ~~~~~~~~~~~~~ A host-built **parameter dialog** (API 1.3), obtained from ``IIqHostUI.CreateDialog``. You describe fields with the ``Add*`` methods, call ``Execute``, then read the entered values with the typed ``Get*`` methods. The host builds and owns every widget; your plugin only describes the form, so there is no cross-runtime UI risk. .. code-block:: pascal { builders — Key identifies the field for the getters; Label_ is the prompt } function AddText (const Key, Label_, Default: PWideChar): TIqResult; cdecl; function AddMultiline(const Key, Label_, Default: PWideChar): TIqResult; cdecl; function AddInt (const Key, Label_: PWideChar; Default, Min, Max: LongInt): TIqResult; cdecl; function AddFloat (const Key, Label_: PWideChar; Default, Min, Max: Double; Decimals: LongInt): TIqResult; cdecl; function AddBool (const Key, Label_: PWideChar; Default: LongBool): TIqResult; cdecl; function AddChoice (const Key, Label_, Options: PWideChar; DefaultIndex: LongInt): TIqResult; cdecl; // Options TAB-separated function AddFile (const Key, Label_, Default, Filter: PWideChar; SaveDlg: LongBool): TIqResult; cdecl; function AddFolder (const Key, Label_, Default: PWideChar): TIqResult; cdecl; function AddColor (const Key, Label_: PWideChar; Default: TIqColor): TIqResult; cdecl; { run modal — OK = True if the user accepted } function Execute(out OK: LongBool): TIqResult; cdecl; { typed getters — valid after Execute } function GetText (const Key: PWideChar; out Value: IIqString): TIqResult; cdecl; // text/multiline/file/folder, and the selected choice text function GetInt (const Key: PWideChar; out Value: LongInt): TIqResult; cdecl; function GetFloat (const Key: PWideChar; out Value: Double): TIqResult; cdecl; function GetBool (const Key: PWideChar; out Value: LongBool): TIqResult; cdecl; function GetChoice(const Key: PWideChar; out Index: LongInt): TIqResult; cdecl; // selected index function GetColor (const Key: PWideChar; out Value: TIqColor): TIqResult; cdecl; Semantics: ``Min = Max`` means an unbounded numeric field; ``Decimals`` sets float display precision; choice ``Options`` is one TAB-separated string and ``GetChoice`` returns the selected index (``GetText`` returns the selected option's text). A mismatched-kind getter returns ``IQ_E_INVALIDARG``, an unknown key ``IQ_E_NOTFOUND``, and a duplicate ``Add`` key ``IQ_E_INVALIDARG``. ``Execute`` is single-shot — a second call returns the first result. When no dialog provider is available (headless), ``Execute`` returns ``OK = False`` and ``MessageBox`` returns ``imrCancel``. .. _plugin-iface-form: .. index:: single: IIqForm ``IIqForm`` ~~~~~~~~~~~ A host-built **live, non-modal tool window** (API 1.4), obtained from ``IIqHostUI.CreateForm``. Unlike the modal ``IIqDialog``, a form stays open after the call returns: you add controls (each ``Add*`` builds a real widget **now** and returns a retained :ref:`IIqControl ` handle), ``Show`` it, then mutate the controls at runtime and react to user input through an :ref:`IIqFormSink `. The host owns the window and every widget — see :ref:`plugin-live-forms`. .. code-block:: pascal { builders — each creates a widget now and returns its retained handle; Key identifies the control for events and FindControl } function AddButton (const Key, Caption: PWideChar; out C: IIqControl): TIqResult; cdecl; function AddLabel (const Key, Caption: PWideChar; out C: IIqControl): TIqResult; cdecl; function AddText (const Key, Label_, Default: PWideChar; out C: IIqControl): TIqResult; cdecl; function AddMultiline(const Key, Label_, Default: PWideChar; out C: IIqControl): TIqResult; cdecl; function AddInt (const Key, Label_: PWideChar; Default, Min, Max: LongInt; out C: IIqControl): TIqResult; cdecl; function AddFloat (const Key, Label_: PWideChar; Default, Min, Max: Double; Decimals: LongInt; out C: IIqControl): TIqResult; cdecl; function AddBool (const Key, Label_: PWideChar; Default: LongBool; out C: IIqControl): TIqResult; cdecl; function AddChoice (const Key, Label_, Options: PWideChar; DefaultIndex: LongInt; out C: IIqControl): TIqResult; cdecl; // Options TAB-separated function AddColor (const Key, Label_: PWideChar; Default: TIqColor; out C: IIqControl): TIqResult; cdecl; function AddFile (const Key, Label_, Default, Filter: PWideChar; SaveDlg: LongBool; out C: IIqControl): TIqResult; cdecl; function AddFolder (const Key, Label_, Default: PWideChar; out C: IIqControl): TIqResult; cdecl; function AddList (const Key, Label_: PWideChar; out C: IIqControl): TIqResult; cdecl; { layout grouping (optional) — controls added between Begin/EndRow share a row } function BeginRow: TIqResult; cdecl; function EndRow: TIqResult; cdecl; { lookup } function FindControl(const Key: PWideChar; out C: IIqControl): TIqResult; cdecl; { lifecycle } function Show: TIqResult; cdecl; function Hide: TIqResult; cdecl; function Close: TIqResult; cdecl; function IsVisible(out Visible: LongBool): TIqResult; cdecl; function SetTitle(const Title: PWideChar): TIqResult; cdecl; Controls are auto-stacked vertically (label + editor per row) in the order added; ``BeginRow`` / ``EndRow`` group several controls onto one line. A duplicate ``Add`` key returns ``IQ_E_INVALIDARG``; ``FindControl`` with an unknown key returns ``IQ_E_NOTFOUND``. After the window closes the form handle (and all its control handles) go **inert** — every call returns ``IQ_E_FAIL`` rather than dereferencing a freed widget. .. _plugin-iface-control: .. index:: single: IIqControl ``IIqControl`` ~~~~~~~~~~~~~~ A retained handle to one live widget (API 1.4), returned by the ``IIqForm`` builders or ``FindControl``. It is a single generic handle whose accessors are **kind-checked**: a getter or setter that does not fit the control's kind returns ``IQ_E_INVALIDARG`` (e.g. ``GetInt`` on a label, or a list operation on a button). .. code-block:: pascal function Key(out S: IIqString): TIqResult; cdecl; function GetText (out Value: IIqString): TIqResult; cdecl; // text/multiline/file/folder; selected choice text; label/button caption function SetText (const Value: PWideChar): TIqResult; cdecl; function GetInt (out Value: LongInt): TIqResult; cdecl; function SetInt (Value: LongInt): TIqResult; cdecl; function GetFloat (out Value: Double): TIqResult; cdecl; function SetFloat (Value: Double): TIqResult; cdecl; function GetBool (out Value: LongBool): TIqResult; cdecl; function SetBool (Value: LongBool): TIqResult; cdecl; function GetChoice(out Index: LongInt): TIqResult; cdecl; function SetChoice(Index: LongInt): TIqResult; cdecl; function GetColor (out Value: TIqColor): TIqResult; cdecl; function SetColor (Value: TIqColor): TIqResult; cdecl; function SetCaption(const Caption: PWideChar): TIqResult; cdecl; // button/label text function SetEnabled(Enabled: LongBool): TIqResult; cdecl; function SetVisible(Visible: LongBool): TIqResult; cdecl; { list-only ops (other kinds → IQ_E_INVALIDARG) } function AppendLine (const Text: PWideChar): TIqResult; cdecl; function Clear: TIqResult; cdecl; function ItemCount (out Count: LongInt): TIqResult; cdecl; function GetSelected(out Index: LongInt): TIqResult; cdecl; function SetSelected(Index: LongInt): TIqResult; cdecl; Once the form closes, the handle is inert and every method returns ``IQ_E_FAIL`` — it is always safe to keep (and even call) a stale handle. .. _plugin-iface-formsink: .. index:: single: IIqFormSink ``IIqFormSink`` ~~~~~~~~~~~~~~~ Implemented by your plugin and handed to ``CreateForm`` (it may be ``nil`` for a form that needs no event handling). The host calls it back when the user interacts with the form. .. code-block:: pascal { IIqFormSink — implemented by your plugin } procedure OnButtonClicked(const Key: PWideChar); cdecl; // a button with that Key was clicked procedure OnValueChanged(const Key: PWideChar); cdecl; // an editor's value changed; read it via the control procedure OnFormClosed; cdecl; // the window closed (fires once) Every callback fires on the **main thread** and is wrapped by a boundary guard in the host, so an exception in your handler cannot escape into IQcad's message loop (identical to the ``IIqEventSink`` bridge). ``OnValueChanged`` carries the control key; read the new value through the control handle (``FindControl`` + a typed getter). .. _plugin-iface-command: .. index:: single: IIqCommand and IIqCommandRegistry single: IIqCommand single: IIqCommandRegistry ``IIqCommand`` and ``IIqCommandRegistry`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You implement ``IIqCommand``; the host provides ``IIqCommandRegistry``. .. code-block:: pascal { IIqCommand — implemented by your plugin } function Name(out S: IIqString): TIqResult; cdecl; function Aliases(out S: IIqString): TIqResult; cdecl; // comma-separated, e.g. 'RC,REC' function Category(out S: IIqString): TIqResult; cdecl; function HelpText(out S: IIqString): TIqResult; cdecl; function Invoke(const Args: IIqEnumerator): TIqResult; cdecl; // Args yields IIqString { IIqCommandRegistry — provided by Host.Commands } function Register(const Cmd: IIqCommand): TIqResult; cdecl; function Unregister(const Name: PWideChar): TIqResult; cdecl; function Execute(const Input: PWideChar): TIqResult; cdecl; // run another command line Aliases are fully supported — typing an alias dispatches the command just like its canonical name. .. _plugin-iface-drawing: .. index:: single: IIqDrawing ``IIqDrawing`` ~~~~~~~~~~~~~~ The active drawing. Methods marked *(1.2)* were added in API 1.2. .. code-block:: pascal function AddLine(const A, B: TIqPoint3D; out E: IIqEntity): TIqResult; cdecl; function AddCircle(const C: TIqPoint3D; R: Double; out E: IIqEntity): TIqResult; cdecl; function AddPolyline(Pts: PIqPoint3D; Count: LongInt; Closed: LongBool; out E: IIqEntity): TIqResult; cdecl; function AddText(const P: TIqPoint3D; const S: PWideChar; H: Double; out E: IIqEntity): TIqResult; cdecl; function AddArc(const AStart, AMid, AEnd: TIqPoint3D; out E: IIqEntity): TIqResult; cdecl; // (1.2) function AddEllipse(const Center: TIqPoint3D; RadX, RadY, Rotation: Double; out E: IIqEntity): TIqResult; cdecl; function AddRectangle(const A, B: TIqPoint3D; out E: IIqEntity): TIqResult; cdecl; // (1.2) function AddHatch(Pts: PIqPoint3D; Count: LongInt; const Pattern: PWideChar; Angle, Scale: Double; out E: IIqEntity): TIqResult; cdecl; // (1.2) function AddTunnelSectionLine(Pts: PIqPoint3D; Count: LongInt; Closed: LongBool; out E: IIqEntity): TIqResult; cdecl; function Entities(out E: IIqEnumerator): TIqResult; cdecl; // yields IIqEntity function Selection(out E: IIqEnumerator): TIqResult; cdecl; // yields IIqEntity function FindByHandle(const H: PWideChar; out E: IIqEntity): TIqResult; cdecl; function Layers(out E: IIqEnumerator): TIqResult; cdecl; // yields IIqLayer function ActiveLayer(out S: IIqString): TIqResult; cdecl; function SetActiveLayer(const Name: PWideChar): TIqResult; cdecl; function CreateLayer(const Name: PWideChar; Color: TIqColor): TIqResult; cdecl; // (1.2) function GetName(out S: IIqString): TIqResult; cdecl; // (1.2) function BeginEdit: TIqResult; cdecl; function EndEdit: TIqResult; cdecl; function AddSurveyPoint(const Pos: TIqPoint3D; Number: LongInt; out E: IIqEntity): TIqResult; cdecl; function Surfaces(out E: IIqEnumerator): TIqResult; cdecl; // yields IIqSurface function CreateSurface(const Name: PWideChar; Pts: PIqPoint3D; Count: LongInt; out S: IIqSurface): TIqResult; cdecl; function FindSurface(const Name: PWideChar; out S: IIqSurface): TIqResult; cdecl; // (1.2) function Alignments(out E: IIqEnumerator): TIqResult; cdecl; // yields IIqAlignment function CreateAlignmentFromPolyline(const PolyHandle: PWideChar; Radius: Double; const Name: PWideChar; out A: IIqAlignment): TIqResult; cdecl; function FindAlignment(const Name: PWideChar; out A: IIqAlignment): TIqResult; cdecl; // (1.2) function ZoomExtents: TIqResult; cdecl; // (1.2) function Invalidate: TIqResult; cdecl; // (1.2) .. index:: single: IIqEntity and IIqLayer single: IIqEntity single: IIqLayer ``IIqEntity`` and ``IIqLayer`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: pascal { IIqEntity } function Handle(out S: IIqString): TIqResult; cdecl; function EntityType(out S: IIqString): TIqResult; cdecl; function Layer(out S: IIqString): TIqResult; cdecl; function SetLayer(const Name: PWideChar): TIqResult; cdecl; function Color: TIqColor; cdecl; function SetColor(C: TIqColor): TIqResult; cdecl; function PointCount: LongInt; cdecl; function GetPoint(Index: LongInt; out P: TIqPoint3D): TIqResult; cdecl; function SetPoint(Index: LongInt; const P: TIqPoint3D): TIqResult; cdecl; function GetProp(const Name: PWideChar; out V: TIqVariant): TIqResult; cdecl; function SetProp(const Name: PWideChar; const V: TIqVariant): TIqResult; cdecl; function Delete: TIqResult; cdecl; { IIqLayer } function Name(out S: IIqString): TIqResult; cdecl; function Color: TIqColor; cdecl; Survey-point properties are accessed through ``GetProp`` / ``SetProp`` using the well-known names ``PointNumber``, ``PointName``, ``Northing``, ``Easting``, ``Elevation``, ``RawDescription``, ``FullDescription``, ``PointGroup``, ``LockPoint``, ``LabelStyleName`` (constants ``IQ_PROP_*`` in the contract). .. _plugin-iface-surface: .. index:: single: Civil: IIqSurface single: IIqProfilePoint single: IIqAlignment Civil: ``IIqSurface``, ``IIqProfilePoint``, ``IIqAlignment`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: pascal { IIqSurface } function Name(out S: IIqString): TIqResult; cdecl; function Handle(out S: IIqString): TIqResult; cdecl; function ElevationAt(const X, Y: Double; out Z: Double): TIqResult; cdecl; function SlopeAt(const X, Y: Double; out SlopePct, SlopeRatio: Double): TIqResult; cdecl; function AddPoint(const P: TIqPoint3D): TIqResult; cdecl; function ProfileAlong(Pts: PIqPoint3D; Count: LongInt; out Profile: IIqEnumerator): TIqResult; cdecl; // yields IIqProfilePoint function VolumeTo(const Other: IIqSurface; Cell: Double; out Cut, Fill: Double): TIqResult; cdecl; { IIqProfilePoint } function Station: Double; cdecl; function Z: Double; cdecl; { IIqAlignment } function Name(out S: IIqString): TIqResult; cdecl; function Handle(out S: IIqString): TIqResult; cdecl; function Length: Double; cdecl; function StartStation: Double; cdecl; function EndStation: Double; cdecl; function PointAtStation(const S: Double; out P: TIqPoint3D): TIqResult; cdecl; function DirectionAtStation(const S: Double; out Radians: Double): TIqResult; cdecl; function ElevationAtStation(const S: Double; out Z: Double): TIqResult; cdecl; function StationOffsetAt(const X, Y: Double; out Station, Offset: Double): TIqResult; cdecl; function Elements(out E: IIqEnumerator): TIqResult; cdecl; // yields IIqAlignmentElement { IIqAlignmentElement } function Kind(out S: IIqString): TIqResult; cdecl; // 'Line' | 'Arc' | 'Spiral' function Length: Double; cdecl; function StartStation: Double; cdecl; function Radius: Double; cdecl; .. index:: single: Events: IIqEventSource and IIqEventSink single: Events: IIqEventSource single: IIqEventSink Events: ``IIqEventSource`` and ``IIqEventSink`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Implement ``IIqEventSink``; subscribe via ``Host.Events`` → ``Subscribe``. Releasing the returned cookie unsubscribes. .. code-block:: pascal { IIqEventSource — from Host.Events } function Subscribe(const Sink: IIqEventSink; out Cookie: IInterface): TIqResult; cdecl; { IIqEventSink — implemented by your plugin (nine callbacks) } procedure OnEntityCreated(const E: IIqEntity); cdecl; procedure OnSelectionChanged(const Sel: IIqEnumerator); cdecl; procedure OnDrawingOpened(const D: IIqDrawing); cdecl; procedure OnDrawingSaved(const D: IIqDrawing); cdecl; procedure OnDrawingClosed(const Name: PWideChar); cdecl; procedure OnEntityModified(const E: IIqEntity); cdecl; procedure OnEntityDeleted(const Handle: PWideChar); cdecl; procedure OnCommandStarted(const Name: PWideChar); cdecl; procedure OnCommandFinished(const Name: PWideChar; Success: LongBool); cdecl; To stop listening, set your stored cookie variable to ``nil``. Each callback is wrapped by a boundary guard in the host, so an exception in your handler cannot escape into IQcad. .. index:: single: Strings single: enumerators single: runnables Strings, enumerators, runnables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. code-block:: pascal { IIqString } function Length: LongInt; cdecl; function Chars: PWideChar; cdecl; // NUL-terminated UTF-16, valid for the object's life { IIqEnumerator — call MoveNext BEFORE Current } function MoveNext: LongBool; cdecl; function Current(out Obj: IInterface): TIqResult; cdecl; // QueryInterface to the concrete type procedure Reset; cdecl; { IIqRunnable — for PostToMainThread } procedure Run; cdecl; Iterate an enumerator like this: .. code-block:: pascal var en: IIqEnumerator; obj: IInterface; ent: IIqEntity; begin if d.Selection(en) = IQ_OK then while en.MoveNext do if (en.Current(obj) = IQ_OK) and Supports(obj, IIqEntity, ent) then ent.SetColor(IqRGB(220, 30, 30)); // recolour each selected entity end; .. _plugin-debugging: .. index:: single: Debugging plugins Debugging plugins ----------------- IQcad routes plugin and scripting activity to a **debug log** so you can diagnose load failures and trace behaviour without a debugger. .. index:: single: Enabling the debug log Enabling the debug log ~~~~~~~~~~~~~~~~~~~~~~~ Set ``"debugLog": true`` in IQcad's ``settings.json``. IQcad then writes ``iqcad_debug.log`` next to the executable, flushed after every line so entries survive a crash. .. index:: single: What gets logged What gets logged ~~~~~~~~~~~~~~~~ - **Plugin lifecycle** (category ``Plugin``) — discovery, each load and activation stage, every ``FAIL``/``EXCEPTION`` with its reason, and deactivation. If a plugin won't load, this log pinpoints exactly where it stopped. - **Plugin command register/unregister** (category ``Plugin``). - **Your ``Host.Log`` calls** — every ``Host.Log(level, msg)`` is written with its level. ``illDebug`` goes *only* to the log (no command-line clutter); ``illInfo`` / ``illWarning`` / ``illError`` go to the log **and** the command line. This gives you a quiet debug channel plus user-visible messages. - **Scripting** (category ``Script``) — script output and command execution. .. index:: single: Discovering plugin commands Discovering plugin commands ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - On load, each registered command is announced on the command line: ``Plugin command available: HELLO (aliases: HI)``. - :command:`HELP` ends with a **Loaded plugin commands** section listing every command added by plugins, with aliases and help text, so users can always see which external commands are available. .. index:: single: Building for other platforms Building for other platforms ---------------------------- The same source builds on all three targets; only the output filename and the manifest ``entry`` key differ: .. list-table:: :header-rows: 1 :widths: 22 26 26 26 * - Platform key - Toolchain - Output name - Example * - ``x86_64-linux`` - lazbuild / fpc, gcc - ``lib.so`` - ``libMyFirstPlugin.so`` * - ``x86_64-win64`` - lazbuild / fpc, MinGW gcc - ``.dll`` - ``MyFirstPlugin.dll`` * - ``aarch64-darwin`` - lazbuild / fpc, clang - ``lib.dylib`` - ``libMyFirstPlugin.dylib`` You may ship one plugin folder containing all three binaries plus a single ``plugin.json`` whose ``entry`` map names each; IQcad loads only the one matching the running platform. .. index:: single: Versioning and compatibility — summary single: Versioning single: compatibility — summary Versioning and compatibility — summary -------------------------------------- - The contract is **API 1.6** (``IQSDK_API_MAJOR`` = 1, ``IQSDK_API_MINOR`` = 6). 1.6 added ribbon & menu contributions (``IIqHostUI.AddRibbonButton`` / ``AddMenuItem``); 1.5 added in-window dockable panels (``IIqHostUI.CreatePanel``); 1.4 added live non-modal forms (``IIqForm`` / ``IIqControl`` / ``IIqFormSink`` + ``CreateForm``); 1.3 added the parameter dialog (``IIqDialog`` / ``CreateDialog``) and the rich ``MessageBox``; 1.2 added the extra ``IIqDrawing`` methods and the full nine-event ``IIqEventSink``. - Declare the minimum you need in ``apiVersion`` — same ``major``, and a ``min_minor`` no higher than the host's. - Shipped interface layouts are frozen; new functionality arrives as appended methods (minor bump) or new interfaces. - Until the SDK is declared stable, **rebuild plugins against the current SDK whenever you update IQcad**.