9.4. Plugins (Native SDK)
IQcad can be extended in two complementary ways. Scripting (Python or
Pascal, chapters Python scripting, Python Scripting Reference and
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.
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.
9.4.1. Plugins versus scripting
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 ( |
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 |
|
|
Both share the same underlying engine, so concepts such as commands, events, layers, and the civil model behave identically.
9.4.2. Architecture and design
9.4.2.1. 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: everyIIq*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.
9.4.2.2. 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 areQueryInterface,AddRef,Release. Objects are reference-counted; the lastReleasefrees 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/Variantparameters 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.
9.4.2.3. 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 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.
9.4.2.4. 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 The manifest (plugin.json)). 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.
9.4.2.5. 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.
9.4.2.6. 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.
9.4.3. The plugin lifecycle
A plugin binary exports exactly one function. Its lifetime is a fixed sequence driven by the loader:
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)
Discover — at startup the loader scans the plugins directory for sub-folders containing a
plugin.json, reads each manifest, and version-gates it.Load — for each enabled and consented compatible plugin, the loader
dlopens the binary and looks up the exportIqcadPluginInit.Init —
IqcadPluginInit(host, out plugin)creates your plugin object and returns it. Thehostyou receive is your handle to everything.Describe — the loader calls
plugin.Describeand verifies the reportedIdmatches the manifest (a mismatch quarantines the plugin).Activate —
plugin.Activate(host)is where you register commands and subscribe to events.Deactivate — on shutdown or when the user disables the plugin,
plugin.Deactivateruns; unregister your commands and release any event-subscription cookies here.
9.4.3.1. Loader states
Each discovered plugin carries a state visible in the Plugin Manager:
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. |
9.4.4. 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.
9.4.4.1. Opening the manager
Command — type PLUGINS (alias
PLUGINMANAGER).Ribbon — Tools → Scripting → Plugins.
Menu — Tools → Plugin Manager…
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.
9.4.4.2. Enabling, disabling, reloading
Enable — select a plugin and click Enable. The first time, a trust dialog appears:
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.
9.4.4.3. 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.
9.4.5. Installing a plugin
Plugins live under a per-user directory:
Platform |
Plugins directory |
|---|---|
Linux / macOS |
|
Windows |
|
Each plugin gets its own sub-folder containing the binary and the manifest:
~/.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.
9.4.6. 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.
Field |
Type |
Meaning |
|---|---|---|
|
int |
Manifest format version — currently always |
|
string |
Required. Reverse-DNS unique id (e.g.
|
|
string |
Display name shown in the Plugin Manager. |
|
string |
The plugin’s own version (informational, e.g. |
|
int |
Required SDK major version (must equal the host major). |
|
int |
Minimum SDK minor version the plugin needs. |
|
string |
Optional publisher name. |
|
string |
Optional one-line description. |
|
string |
Optional icon filename (reserved). |
|
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:
{
"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.
9.4.7. 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.
9.4.7.1. 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 souIqSdkanduIqSdkRtlresolve.
9.4.7.2. Step 1 — write the source
Create MyFirstPlugin.lpr:
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 IIqPlugin.
One or more classes implementing IIqCommand.
The single exported
IqcadPluginInitreturning the plugin via itsoutparameter.
9.4.7.3. Step 2 — write the manifest
Create plugin.json beside the source (see The manifest (plugin.json) for
all fields):
{
"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"
}
}
9.4.7.4. Step 3 — build
With a Lazarus project file (.lpi) whose other unit files path
includes cad/plugins, build the library:
lazbuild MyFirstPlugin.lpi
or directly with the compiler, pointing -Fu at the SDK units:
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.
9.4.7.5. Step 4 — install and enable
Copy the binary and plugin.json into a sub-folder of the plugins
directory:
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 PLUGINS, select My First Plugin, click Enable, and accept the trust prompt. The command line confirms:
Plugin command available: HELLO (aliases: HI)
9.4.7.6. Step 5 — run it
Type HELLO (or the alias HI) at the command line. You will see:
Hello from my first IQcad plugin!
Type HELP and scroll to the Loaded plugin commands section to see your command listed alongside any other plugins’.
9.4.7.7. Troubleshooting
Plugin not listed — the folder must contain a valid
plugin.jsonwith a non-emptyid. Click Reload.Status “Incompatible” — no
entryfor your platform key, or the requiredapiVersionis newer than this IQcad. Check the details pane.Status “Failed” — the binary could not be loaded, the
IqcadPluginInitexport is missing, or theDescribeid does not match the manifest. The exact error is in the details pane and the debug log.Enabled but nothing happens — confirm the command was announced on the command line; check
HELP.
9.4.8. 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.
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:
IIqDrawingis reached throughHost.ActiveDrawing. It offersAddLine,AddCircle,AddArc,AddRectangle,AddText,AddHatchand more (see IIqDrawing).Colours are RGB, not ACI indices. Use
IqRGB(r, g, b)(Pascal) orIQ_RGB(r, g, b)(C). Passing a small integer like3yieldsRGB(3,0,0)— almost black — a classic mistake.Call
Invalidate(and optionallyZoomExtents) so the view refreshes.
Civil work follows the same pattern: AddSurveyPoint,
CreateSurface, IIqSurface.ElevationAt, alignment queries — see
Civil: IIqSurface, IIqProfilePoint, IIqAlignment and the civilui sample.
9.4.9. 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 Plugins (Native SDK) intro). Instead the host builds the UI: you describe a dialog and IQcad creates, shows, and reads back every widget.
9.4.9.1. The rich message box
IIqHostUI.MessageBox takes a caption, a button set, and an icon, and
returns which button the user pressed:
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.
9.4.9.2. A parameter dialog
Build a form, run it, read the values:
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 API reference, and the complete worked example in the
dialogdemo / cdialogdemo sample.
9.4.9.3. 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 Python Scripting Reference):
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 Pascal Scripting):
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;
9.4.10. Live tool windows
A parameter dialog (Parameter dialogs and message boxes) 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 IIqForm builder; each Add* creates a real widget now and
hands back a retained IIqControl handle. You
pass an 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
IIqFormhandle.When the window closes — the user clicks
[X], you callClose, your plugin is deactivated, or IQcad exits — the host firesOnFormClosedonce, 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.
9.4.10.2. 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 Python Scripting 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:
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 Pascal Scripting) — CreateForm(title) returns
a TScriptForm; assign script procedure names to its event handlers:
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;
9.4.10.3. 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 IIqForm builder, same
IIqControl handles and the same
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.
{ 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 Python Scripting Reference) and
PascalScript (CreatePanel(title) — see Pascal Scripting), each
mirroring its floating create_form / CreateForm counterpart.
9.4.12. 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:
#include "iqcad_sdk.h"
#include <string.h>
/* ---- 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:
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.
9.4.13. 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:
# |
Pascal |
C twin |
Command (alias) |
Demonstrates |
|---|---|---|---|---|
1 |
|
|
|
The minimal skeleton: entry point, |
2 |
|
|
|
Drawing geometry: |
3 |
|
|
|
Selection and entities: iterate |
4 |
|
|
(none — a listener) |
Events: |
5 |
|
|
|
Civil + UI: |
6 |
|
|
|
Parameter dialog: |
7 |
|
|
|
Live tool window: |
8 |
|
|
|
Docked panel: same as |
9 |
|
|
|
Adds a ribbon button + menu item (with a PNG icon) that run a command:
|
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.
9.4.14. 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.
9.4.14.1. Result codes (TIqResult)
Constant |
Value |
Meaning |
|---|---|---|
|
0 |
Success. |
|
-1 |
Generic failure. |
|
-2 |
Method not implemented for this object. |
|
-3 |
A bad argument was supplied. |
|
-4 |
Requested item does not exist. |
|
-5 |
Index out of range. |
|
-6 |
Called from a thread other than the main thread. |
9.4.14.2. Core types
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.
9.4.14.3. Runtime helpers (uIqSdkRtl)
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.
9.4.14.4. Entry point
// exported under the name 'IqcadPluginInit'
function IqcadPluginInit(const Host: IIqHost; out Plugin: IIqPlugin): TIqResult; cdecl;
C: iq_result IqcadPluginInit(IIqHost* host, IIqPlugin** out_plugin);
9.4.14.5. IIqPlugin
The object your IqcadPluginInit returns.
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
9.4.14.6. IIqHost
Your handle to IQcad, passed to IqcadPluginInit and Activate.
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;
9.4.14.7. IIqHostUI
Prompts, message boxes, and parameter dialogs. All UI is built and owned by the host (the plugin never touches LCL — see Parameter dialogs and message boxes).
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)
9.4.14.8. 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.
{ 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.
9.4.14.9. 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 IIqControl
handle), Show it, then mutate the controls at runtime and react to user
input through an IIqFormSink. The host owns
the window and every widget — see Live tool windows.
{ 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.
9.4.14.10. 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).
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.
9.4.14.11. 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.
{ 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).
9.4.14.12. IIqCommand and IIqCommandRegistry
You implement IIqCommand; the host provides IIqCommandRegistry.
{ 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.
9.4.14.13. IIqDrawing
The active drawing. Methods marked (1.2) were added in API 1.2.
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)
9.4.14.14. IIqEntity and IIqLayer
{ 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).
9.4.14.15. Civil: IIqSurface, IIqProfilePoint, IIqAlignment
{ 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;
9.4.14.16. Events: IIqEventSource and IIqEventSink
Implement IIqEventSink; subscribe via Host.Events →
Subscribe. Releasing the returned cookie unsubscribes.
{ 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.
9.4.14.17. Strings, enumerators, runnables
{ 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:
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;
9.4.15. Debugging plugins
IQcad routes plugin and scripting activity to a debug log so you can diagnose load failures and trace behaviour without a debugger.
9.4.15.1. 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.
9.4.15.2. What gets logged
Plugin lifecycle (category
Plugin) — discovery, each load and activation stage, everyFAIL/EXCEPTIONwith 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.illDebuggoes only to the log (no command-line clutter);illInfo/illWarning/illErrorgo 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.
9.4.15.3. Discovering plugin commands
On load, each registered command is announced on the command line:
Plugin command available: HELLO (aliases: HI).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.
9.4.16. Building for other platforms
The same source builds on all three targets; only the output filename and
the manifest entry key differ:
Platform key |
Toolchain |
Output name |
Example |
|---|---|---|---|
|
lazbuild / fpc, gcc |
|
|
|
lazbuild / fpc, MinGW gcc |
|
|
|
lazbuild / fpc, clang |
|
|
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.
9.4.17. 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 richMessageBox; 1.2 added the extraIIqDrawingmethods and the full nine-eventIIqEventSink.Declare the minimum you need in
apiVersion— samemajor, and amin_minorno 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.