9.3. Pascal Scripting
This chapter is the complete reference for IQcad’s embedded Pascal Script host: every script-facing class, global, command, and event, with a text class diagram and worked examples.
For the big-picture scripting concepts (the trust model, autoload, custom commands, events) see Python scripting in chapter 18 and the Python Scripting Reference in chapter 19. The Pascal host shares the same Layer-2 drawing model as Python, so those concepts carry over directly; this chapter documents the Pascal-specific surface.
9.3.1. Intro
Pascal Script is a Pascal-syntax interpreter that compiles your script
into IQcad at run time. Unlike the Python host – which embeds a
bundled CPython interpreter – the Pascal host has zero external
runtime dependency: there is nothing to install and nothing ships
alongside IQcad.exe. If you already know Object Pascal (or
Delphi), this is the lowest-friction way to automate IQcad.
Both hosts talk to the same Layer-2 drawing model: a surface, alignment, or entity created from Pascal is identical to one created from Python or from the GUI. The two languages differ only in syntax and in how they pass collections (see The object model).
9.3.2. Running Pascal
Four command-line entry points drive the Pascal host:
Command |
Description |
|---|---|
|
Run a |
|
Evaluate a single Pascal expression and print its value at
the command line (e.g. |
|
Run a file and leave it session-resident: any commands or event handlers it registers stay active until you close IQcad. |
|
List the Pascal-defined commands currently registered. |
A script is a normal Pascal program:
program demo;
begin
WriteLine('hello from Pascal');
end.
A bare snippet without a program ... begin ... end. header is
auto-wrapped, so WriteLine('hi'); runs on its own.
Note
The wrapper detects a full program by the first program (or
unit) keyword, skipping any leading comments. You can open a
full-program file with a header comment block ({ ... }, // or
(* ... *)) before the program line, as the bundled samples do.
9.3.2.1. Autoload and trust
Any .pas file dropped in %APPDATA%\IQcad\scripts\ is loaded
automatically at startup, using the same content-hash trust model
as Python. The first time IQcad sees a script (and again whenever its
SHA-256 content hash changes) it shows a one-time trust dialog offering
Always, This session, or Never; your choice is remembered in
%APPDATA%\IQcad\scripts\script_trust.json. A script you have never
trusted is not executed.
9.3.2.2. Responsiveness and Esc
Scripts run on the GUI thread. In any loop that may run for a while,
call Yield periodically: it pumps the message queue so the window
stays responsive and lets Esc cancel the script. Cancellation is
cooperative – it takes effect at the next Yield (or at the next
script line between statements).
9.3.3. The object model
ActiveDrawing returns the current Drawing, which is the factory
for everything else: it creates and returns Entity, Surface,
and Alignment objects. Three helper objects – Points,
Profile, and Elements – carry collections.
globals
|
|-- flat creators ...... AddLine/AddCircle/AddArc/AddEllipse/
| AddRectangle/AddText/AddSurveyPoint/
| AddPolyline/AddHatch/AddTunnelSectionLine
| (return a handle STRING),
| CreateLayer/SetActiveLayer/ZoomExtents
|-- WriteLine / Cmd / Yield
|-- RegisterCommand / UnregisterCommand
|-- OnEvent / OffEvent
|-- dialogs ............ ShowMessage/Confirm/InputString/SelectFile
|-- NewPoints --------> Points (the array-free input builder)
|
`-- ActiveDrawing ----> Drawing
|
| create/returns
|
+--------------+----------------+
| | |
v v v
Entity Surface Alignment
(geometry) (TIN) (horizontal)
AddLine/Circle/Arc/Ellipse/Rectangle/Polyline/Text/
SurveyPoint/Hatch/TunnelSectionLine --> Entity
CreateSurface / FindSurface / SurfaceAt --> Surface
CreateAlignmentFromPolyline /
FindAlignment / AlignmentAt --> Alignment
Surface.ProfileAlong --> Profile (Count + Station(i)/Z(i))
Alignment.Elements --> Elements (Count + Kind(i)/...)
The array-free design. PascalScript handles dynamic arrays poorly,
so the Pascal API never takes or returns an array. Point input uses
a Points builder (NewPoints then Add(x, y, z)); collection
results come back as Profile and Elements objects that expose
a Count plus indexer methods (Station(i) / Z(i) /
Kind(i) …). This is the key difference from Python, which uses
native lists and tuples.
Note
A flat global creator (e.g. AddCircle(...)) returns the new
entity’s handle string, whereas the OO method
ActiveDrawing.AddCircle(...) returns an Entity object you can
keep editing.
9.3.4. Class and global reference
9.3.4.1. Drawing
Obtained from ActiveDrawing. The factory for entities, surfaces,
and alignments.
Member |
Signature |
Returns |
Description |
|---|---|---|---|
|
property (read-only) |
|
Drawing name. |
|
property (read/write) |
|
Name of the current layer. |
|
|
|
Line between two points. |
|
|
|
Circle, centre + radius. |
|
|
|
Three-point arc. |
|
|
|
Ellipse with semi-axes and rotation. |
|
|
|
Rectangle from opposite corners. |
|
|
|
Text s at the point, height h. |
|
|
|
Survey point numbered num. |
|
|
|
Polyline through a |
|
|
|
Hatch over a |
|
|
|
Tunnel section line. |
|
|
|
Create a layer (color is an ACI index). |
|
|
|
Look up an entity by handle ( |
|
|
– |
Zoom to drawing extents. |
|
|
|
Build a TIN from a |
|
|
|
Look up a surface by name ( |
|
|
|
Number of surfaces. |
|
|
|
Surface by zero-based index. |
|
|
|
Build an alignment from a polyline entity. |
|
|
|
Like |
|
|
|
Look up an alignment by name ( |
|
|
|
Number of alignments. |
|
|
|
Alignment by zero-based index. |
9.3.4.2. Entity
A single drawing object returned by the OO Add* methods or by
EntityByHandle.
Member |
Signature |
Returns |
Description |
|---|---|---|---|
|
property (read-only) |
|
Persistent handle. |
|
property (read-only) |
|
|
|
property (read/write) |
|
Layer name. |
|
property (read/write) |
|
ACI colour index. |
|
|
|
Read a typed property (e.g. circle |
|
|
– |
Write a typed property. |
|
|
– |
Remove the entity; later access is a no-op guard. |
9.3.4.3. Surface
A TIN surface created by Drawing.CreateSurface (or looked up via
FindSurface / SurfaceAt).
Member |
Signature |
Returns |
Description |
|---|---|---|---|
|
property (read-only) |
|
Surface name. |
|
property (read-only) |
|
Persistent handle. |
|
|
|
Interpolated Z in z; False outside the TIN. |
|
|
– |
Insert a vertex and rebuild the TIN. |
|
|
|
Long-section along a |
|
|
|
Grid-approx cut/fill; cut where self is above Other. |
9.3.4.4. Alignment
A horizontal alignment created by CreateAlignmentFromPolyline (or
looked up via FindAlignment / AlignmentAt).
Member |
Signature |
Returns |
Description |
|---|---|---|---|
|
property (read-only) |
|
Alignment name. |
|
property (read-only) |
|
Persistent handle. |
|
property (read-only) |
|
Total length. |
|
property (read-only) |
|
Start chainage. |
|
property (read-only) |
|
End chainage. |
|
|
– |
World point at chainage s. |
|
|
|
Bearing at s, in radians. |
|
|
|
Profile elevation at s. |
|
|
– |
Nearest station + offset ( |
|
|
|
Geometry-elements collection. |
9.3.4.5. Points, Profile, Elements
The array-free helper objects. Points is the input builder;
Profile and Elements are result collections.
Object |
Member |
Signature |
Returns |
|---|---|---|---|
|
|
|
– |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
9.3.4.6. Module-level globals
Call these unqualified. The flat creators mirror the Drawing
methods but return a handle string instead of an object.
Member |
Signature |
Returns |
Description |
|---|---|---|---|
|
|
|
The current drawing. |
|
|
|
A fresh, empty |
|
|
– |
Print a line to the command line. |
|
|
|
Run a command-line string. |
|
|
– |
Pump the UI; allow Esc to cancel. |
|
|
|
Flat line; returns a handle. |
|
|
|
Flat circle; returns a handle. |
|
|
|
Flat arc; returns a handle. |
|
|
|
Flat ellipse; returns a handle. |
|
|
|
Flat rectangle; returns a handle. |
|
|
|
Flat text; returns a handle. |
|
|
|
Flat survey point; returns a handle. |
|
|
|
Flat polyline; returns a handle. |
|
|
|
Flat hatch; returns a handle. |
|
|
|
Flat tunnel section line; returns a handle. |
|
|
|
Create a layer (ACI color). |
|
|
– |
Set the current layer. |
|
|
– |
Zoom to drawing extents. |
|
|
– |
Register a command bound to a script proc. |
|
|
– |
Remove a registered command. |
|
|
– |
Attach an event handler by proc name. |
|
|
– |
Detach an event handler. |
9.3.5. Commands and events
9.3.5.1. Custom commands
RegisterCommand binds a new IQcad command to a script procedure
by name. Load the script resident with PASLOAD (or drop it in
the autoload folder) and the command can be typed at the prompt like
any built-in.
program ext;
procedure DoGrid(arg: string);
begin
{ arg arrives as a string -- parse it yourself }
AddCircle(0, 0, 0, StrToFloatDef(arg, 1.0));
end;
begin
RegisterCommand('PASGRID', 'PG', 'Tools', 'Draw a grid', 'DoGrid');
end.
Warning
Command arguments arrive as a string. Parse them yourself with
StrToFloatDef / StrToIntDef.
Remove a registration with UnregisterCommand('PASGRID').
9.3.5.2. Events
OnEvent attaches a handler procedure (by name) to a drawing event.
Event name |
Handler signature |
Fires when |
|---|---|---|
|
|
An entity is created. |
|
|
An entity is deleted. |
|
|
The selection changes. |
Detach a handler with OffEvent(eventName, procname).
Note
Handlers run synchronously on the GUI thread. An exception inside
a handler is logged to the command line, not fatal. A handler that
itself creates entities re-fires entity_created and can recurse –
guard with a module-level boolean (see samples\pascal\03_events.pas).
9.3.6. Dialogs
Four dialog globals – shared with the Python host – prompt the user. Run headlessly (no GUI), they return safe defaults so an unattended script never blocks.
Function |
Signature |
Headless default |
|---|---|---|
|
|
no-op |
|
|
|
|
|
|
|
|
|
SelectFile’s filter is a pipe-delimited Caption|Mask pair
(e.g. 'CSV files|*.csv'); save selects an Open vs Save dialog.
9.3.7. Available RTL
The host registers a subset of the Pascal RTL plus a few additions. Locale-invariant float conversions are provided so that scripts behave identically regardless of the system decimal separator.
Strings:
Copy,Pos,Length,UpperCase,LowerCase,Trim.Numbers:
IntToStr,StrToInt,StrToIntDef,FloatToStr,StrToFloat,StrToFloatDef(the float ones are locale-invariant – always.as the decimal point).Math:
Sin,Cos,Sqrt,Round,Trunc,Abs,Pi,ArcTan,Power.Formatting:
FmtFloat(v: Double; decimals: Integer): string– a locale-invariant fixed-decimal formatter.
Warning
Format (the array of const variant) is not available.
Build strings with FmtFloat + IntToStr + concatenation, e.g.
WriteLine('STA ' + FmtFloat(s, 1) + ' Z=' + FmtFloat(z, 3)).
9.3.8. Examples
Seven worked scripts ship under samples\pascal\:
samples\pascal\01_primitives.pas– geometry, layers, the OO vs flat creators,Pointsfor polyline + hatch.samples\pascal\02_command.pas– registering a custom command.samples\pascal\03_events.pas– wiring up event handlers.samples\pascal\04_surface.pas– TIN surfaces: create, query, profile, volume.samples\pascal\05_alignment.pas– alignments from a polyline.samples\pascal\06_civil_workflow.pas– chainage along an alignment while sampling a surface (the highlight).samples\pascal\07_dialogs.pas– the four user-interaction dialogs.
9.3.8.1. Surfaces
Create a TIN from seed points, interpolate it, take a long-section, then
compare two surfaces by volume (04_surface.pas):
program surface;
var
d: Drawing;
pts, line: Points;
eg, fg: Surface;
prof: Profile;
z, cut, fill: Double;
i: Integer;
begin
d := ActiveDrawing;
pts := NewPoints;
pts.Add(0, 0, 10.0); pts.Add(50, 0, 11.0); pts.Add(50, 40, 12.5);
pts.Add(0, 40, 10.5); pts.Add(25, 20, 11.2);
eg := d.CreateSurface('EG', pts);
if eg.ElevationAt(25, 20, z) then { False outside the TIN }
WriteLine('Z at (25,20) = ' + FmtFloat(z, 3));
eg.AddPoint(25, 40, 11.8); { rebuilds the TIN }
line := NewPoints;
line.Add(0, 20, 0); line.Add(50, 20, 0);
prof := eg.ProfileAlong(line);
for i := 0 to prof.Count - 1 do
WriteLine('STA ' + FmtFloat(prof.Station(i), 3) +
' Z ' + FmtFloat(prof.Z(i), 3));
pts := NewPoints;
pts.Add(0, 0, 11.0); pts.Add(50, 0, 12.0); pts.Add(50, 40, 13.5);
pts.Add(0, 40, 11.5); pts.Add(25, 20, 12.2);
fg := d.CreateSurface('FG', pts);
if eg.VolumeTo(fg, 1.0, cut, fill) then
WriteLine('cut=' + FmtFloat(cut, 2) + ' fill=' + FmtFloat(fill, 2));
end.
9.3.8.2. Alignments
Build an alignment from a polyline (fillet radius required for 3+
vertices), then query it (05_alignment.pas):
program alignment;
var
d: Drawing;
pts: Points;
e: Entity;
al: Alignment;
el: Elements;
x, y, z, dir, st, off: Double;
i: Integer;
begin
d := ActiveDrawing;
pts := NewPoints;
pts.Add(0, 0, 0); pts.Add(100, 0, 0);
pts.Add(100, 50, 0); pts.Add(160, 50, 0);
e := d.AddPolyline(pts, False);
al := d.CreateAlignmentFromPolyline(e.Handle, 20.0, 'CL-1');
WriteLine('len=' + FmtFloat(al.Length, 3) +
' STA ' + FmtFloat(al.StartStation, 3) +
'..' + FmtFloat(al.EndStation, 3));
al.PointAtStation(50.0, x, y, z);
dir := al.DirectionAtStation(50.0); { radians }
al.StationOffsetAt(50.0, 5.0, st, off); { offset - left / + right }
el := al.Elements;
for i := 0 to el.Count - 1 do
WriteLine(el.Kind(i) +
' len=' + FmtFloat(el.Length(i), 3) +
' R=' + FmtFloat(el.Radius(i), 3));
end.
9.3.8.3. Combined civil workflow
The highlight: step chainage along an alignment and sample the ground
surface at each station (06_civil_workflow.pas):
program civil_workflow;
var
d: Drawing;
pts: Points;
e: Entity;
surf: Surface;
al: Alignment;
step, s, x, y, z, gz: Double;
begin
d := ActiveDrawing;
pts := NewPoints;
pts.Add(-20, -20, 10.0); pts.Add(180, -20, 14.0); pts.Add(180, 80, 16.0);
pts.Add(-20, 80, 11.0); pts.Add(80, 25, 13.0);
surf := d.CreateSurface('EG', pts);
pts := NewPoints;
pts.Add(0, 0, 0); pts.Add(100, 0, 0);
pts.Add(100, 50, 0); pts.Add(160, 50, 0);
e := d.AddPolyline(pts, False);
al := d.CreateAlignmentFromPolyline(e.Handle, 20.0, 'CL-1');
step := 10.0;
s := al.StartStation;
while s <= al.EndStation + 1e-6 do
begin
al.PointAtStation(s, x, y, z);
if surf.ElevationAt(x, y, gz) then { False outside the TIN }
WriteLine('STA ' + FmtFloat(s, 1) + ' surf=' + FmtFloat(gz, 3))
else
WriteLine('STA ' + FmtFloat(s, 1) + ' surf=(outside TIN)');
s := s + step;
Yield; { keep responsive / allow Esc }
end;
end.
9.3.9. Dialogs and message boxes
Pascal scripts can pop a host-built parameter dialog or message box — the same UI the plugin SDK uses.
CreateDialog(title): TScriptDialog returns a dialog; describe fields,
Execute, then read the values:
var
dlg: TScriptDialog;
shape: Integer; size: Double; layer: string; col: Integer; filled: Boolean;
begin
dlg := CreateDialog('Draw a shape');
dlg.AddChoice('shape', 'Shape', 'Rectangle'#9'Circle', 0); { TAB separates options }
dlg.AddFloat ('size', 'Size (m)', 10, 0, 0, 2); { Min=Max=0 → unbounded }
dlg.AddText ('layer', 'Layer name', 'SHAPES');
dlg.AddColor ('color', 'Color', $00B400); { 0x00BBGGRR }
dlg.AddBool ('filled','Filled?', False);
if dlg.Execute then
begin
shape := dlg.GetChoice('shape'); { selected index }
size := dlg.GetFloat('size');
layer := dlg.GetText('layer');
col := dlg.GetColor('color');
filled := dlg.GetBool('filled');
end;
end.
Builders: AddText, AddMultiline, AddInt(…, Min, Max),
AddFloat(…, Min, Max, Decimals), AddBool, AddChoice(Key, Caption,
Options, DefaultIndex), AddFile(…, Filter, SaveDlg), AddFolder,
AddColor. Getters: GetText (also the selected choice’s text),
GetInt, GetFloat, GetBool, GetChoice (index), GetColor.
Execute returns False if the user cancels.
MessageBox(Text, Caption: string; Buttons, Icon: Integer): Integer
shows a message box and returns the pressed button. Integer codes —
buttons: 0 OK, 1 OK/Cancel, 2 Yes/No, 3 Yes/No/Cancel;
icons: 0 none, 1 info, 2 warning, 3 error, 4 question;
results: 0 OK, 1 Cancel, 2 Yes, 3 No (a dismissed box
returns 1):
if MessageBox('Delete selection?', 'Confirm', 2, 4) = 2 then { YesNo, question → Yes }
WriteLn('confirmed');
9.3.10. Live forms
A live form is a non-modal, host-owned tool window that stays open: you build controls, keep references, mutate them at runtime, and react to the user through event handlers. It is the scripting face of the plugin SDK’s live tool windows.
CreateForm(title): TScriptForm returns a live form. Its Add* builders
(AddButton, AddLabel, AddText, AddMultiline, AddInt,
AddFloat, AddBool, AddChoice, AddColor, AddFile,
AddFolder, AddList) each return a TScriptControl with typed
Get* / Set* accessors and list ops; the form also has BeginRow /
EndRow, FindControl, Show / Hide / Close / IsVisible /
SetTitle. Assign the name of a script procedure to OnButtonClicked
/ OnValueChanged / OnFormClosed and the host calls it back:
var frm: TScriptForm;
procedure HandleClick(const Key: string);
var n: Integer;
begin
if Key = 'add' then
begin
n := frm.FindControl('n').GetInt;
frm.FindControl('log').AppendLine('Added: ' + IntToStr(n));
frm.FindControl('status').SetText('Last added: ' + IntToStr(n));
end;
end;
begin
frm := CreateForm('Form demo');
frm.AddLabel('status', 'Click Add to append a value');
frm.AddInt('n', 'Value', 1, 0, 1000);
frm.AddButton('add', 'Add');
frm.AddList('log', 'Log');
frm.OnButtonClicked := 'HandleClick'; { name of the script procedure }
frm.Show;
end.
CreatePanel(title): TScriptForm is identical to CreateForm but docks
the panel into the main window — it appears as a tab in the right-side dock strip
instead of a floating window. The Add* builders and On* event handlers
are exactly the same; only the container differs:
frm := CreatePanel('Form demo'); { docked twin of CreateForm }
9.3.12. Limitations and tips
PASEVAL is single-expression. It evaluates one Pascal expression and prints its value; it is not a place for statements.
No ``Format`` / ``array of const``. Build strings with
FmtFloat+IntToStr+ concatenation.Array-free API. Pass points via a
Pointsbuilder; read collection results viaProfile/ElementsCount+ indexers. There are no array parameters or array results.Alignment fillet radius.
CreateAlignmentFromPolylinewithradius = 0(the default) builds straight tangents through every vertex, including polylines with 3 or more vertices. Pass a non-zero radius (at least0.01) only if you want the interior corners filleted into arcs.``ElevationAt`` returns False outside the TIN. Always check the result before using the out-parameter.
Offset sign.
StationOffsetAtreturns an offset that is negative to the left and positive to the right of the alignment direction.Command args are strings. Parse with
StrToFloatDef/StrToIntDef.Limited event coverage. Only
entity_created,entity_deleted, andselection_changedexist; there is noon_entity_modifiedand no drawing-lifecycle or command events yet.No background / async execution. Scripts run on the GUI thread; call
Yieldto keep long loops responsive and Esc-cancelable.