9.2. Python Scripting Reference

This chapter is the complete reference for IQcad’s embedded iqcad Python module: every class, method, property, decorator, and event, with text-based class diagrams and worked examples.

For a getting-started introduction (running scripts, the trust model, custom commands, the big picture) see Python scripting in chapter 18. This chapter is the API reference you reach for once you know the basics.

9.2.1. Intro

IQcad embeds a private, bundled Python 3.12 interpreter that ships in the python\ folder next to IQcad.exe. There is nothing to install and no system Python is required or used. Every script runs with a built-in iqcad module already imported, exposing an object-oriented drawing API rooted at iqcad.active_drawing together with a set of flat convenience helpers.

The reference below is generated from the type stub samples\python\iqcad.pyi and matches it exactly. Point your IDE at that stub (or the bundled interpreter) for autocomplete.

9.2.2. Running Python

Five command-line entry points drive the Python host:

Command

Description

PYTHON <file.py> (alias PY)

Run a .py file once. Anything it registers is discarded when it finishes.

PYEVAL <expr>

Evaluate a single Python expression and print the result at the command line (e.g. PYEVAL iqcad.active_drawing.name).

PYLOAD <file.py>

Run a file and leave it session-resident: any @iqcad.command commands or event handlers it registers stay active until you close IQcad.

PYSCRIPTS

List the Python-defined commands currently registered.

PYPIP <args>

Forward arguments to pip running inside the bundled interpreter.

Warning

PYPIP install <pkg> downloads from PyPI and therefore needs network access. Behind a TLS-intercepting proxy or antivirus the SSL handshake may fail. Install from an offline wheel (PYPIP install C:\wheels\somepkg.whl) or pass pip’s proxy / trust options (PYPIP install --proxy http://host:port pkg or --trusted-host pypi.org --trusted-host files.pythonhosted.org).

9.2.2.1. Autoload and trust

Any .py file dropped in %APPDATA%\IQcad\scripts\ is loaded automatically at startup. Because autoloaded code runs without prompting, IQcad shows a one-time content-trust dialog the first time it sees a script (and again whenever its SHA-256 content hash changes). The dialog offers 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.2.2.2. Responsiveness and Esc

Scripts run on the GUI thread, so a tight loop blocks the UI for its whole duration. In any loop that may run for a while, call iqcad.yield_() periodically: it pumps the message queue so the window stays responsive, and lets Esc cancel the script (raising a catchable KeyboardInterrupt). The method is spelled with a trailing underscore because yield is a Python keyword.

9.2.2.3. Type stubs

For autocomplete and type checking, point your editor at the stub samples\python\iqcad.pyi or at the bundled interpreter in python\.

9.2.3. The object model

The iqcad module is the root. It carries flat helper functions, the command/event decorators, and the live active_drawing attribute. active_drawing resolves fresh on every access (PEP 562) and returns the current Drawing. The Drawing is the factory for everything else: it creates and returns Entity, Surface, and Alignment objects.

iqcad  (module)
|
|-- flat helpers ....... cmd, write_line, point,
|                        add_line/add_circle/add_arc/add_ellipse/
|                        add_rectangle/add_hatch/
|                        add_tunnel_section_line  (return handle str),
|                        create_layer, set_active_layer, zoom_extents
|-- decorators ......... command, on_entity_created,
|                        on_entity_deleted, on_selection_changed,
|                        on / off, unregister_command
|-- yield_() ........... pump UI / allow Esc
|
`-- active_drawing  --->  Drawing   (resolves fresh each access)
                           |
                           |  create/returns
                           |
           +---------------+----------------+
           |               |                |
           v               v                v
        Entity          Surface          Alignment
        (geometry)      (TIN)            (horizontal/vertical)

        add_line/circle/arc/ellipse/rectangle/polyline/
        text/hatch/survey_point/tunnel_section_line  --> Entity
        create_surface / find_surface / surfaces       --> Surface
        create_alignment_from_polyline /
        find_alignment / alignments                    --> Alignment

Note

iqcad.add_line(...) (flat) returns a handle string, whereas iqcad.active_drawing.add_line(...) (OO) returns an Entity. add_polyline is OO-only: there is no flat iqcad.add_polyline.

9.2.4. Class reference

9.2.4.1. Drawing

Obtained from iqcad.active_drawing. The factory for entities, surfaces, and alignments.

Member

Signature

Returns

Description

active_layer

property (read/write)

str

Name of the current layer.

name

property (read-only)

str

Drawing name.

layer_names

property (read-only)

List[str]

All layer names.

add_line

add_line(a, b)

Entity

Line from point a to b.

add_circle

add_circle(c, r)

Entity

Circle, centre c, radius r.

add_text

add_text(pos, text, height)

Entity

Text at pos with height.

add_polyline

add_polyline(pts, closed=False)

Entity

Polyline through pts (OO-only).

add_survey_point

add_survey_point(pos, number)

Entity

Survey point with number.

add_arc

add_arc(start, mid, end)

Entity

Three-point arc.

add_ellipse

add_ellipse(center, radx, rady, rotation=0.0)

Entity

Ellipse with semi-axes.

add_rectangle

add_rectangle(c1, c2)

Entity

Rectangle from opposite corners.

add_hatch

add_hatch(points, pattern="ANSI31", angle=0.0, scale=1.0)

Entity

Hatch filling the points boundary.

add_tunnel_section_line

add_tunnel_section_line(points, closed=False)

Entity

Tunnel section line.

create_layer

create_layer(name, color=...)

bool

Create a layer; color is an ACI index.

find_by_handle

find_by_handle(h)

Optional[Entity]

Look up an entity by handle.

entities

entities()

List[Entity]

All entities in the drawing.

selection

selection()

List[Entity]

Currently selected entities.

zoom_extents

zoom_extents()

None

Zoom to drawing extents.

invalidate

invalidate()

None

Force a redraw.

surfaces

surfaces()

List[Surface]

All surfaces.

find_surface

find_surface(name)

Optional[Surface]

Look up a surface by name.

create_surface

create_surface(name, pts)

Surface

Build a TIN from (x, y, z) points.

alignments

alignments()

List[Alignment]

All alignments.

find_alignment

find_alignment(name)

Optional[Alignment]

Look up an alignment by name.

create_alignment_from_polyline

create_alignment_from_polyline(handle, radius=0.0, name="")

Alignment

Build an alignment from a polyline entity.

9.2.4.2. Entity

A single drawing object returned by the OO add_* methods.

Member

Signature

Returns

Description

handle

property (read-only)

str

Persistent handle.

type_name

property (read-only)

str

'Line' | 'Circle' | 'Polyline' | 'Text' | 'SurveyPoint' | …

layer

property (read/write)

str

Layer name.

color

property (read/write)

int

ACI colour index.

points

property (read/write)

List[Tuple[float, float, float]]

Defining points.

delete

delete()

None

Remove the entity. Any later access raises RuntimeError.

get_property

get_property(name)

bool | int | float | str | None

Read a typed property (e.g. circle 'Radius').

set_property

set_property(name, value)

None

Write a typed property.

Common get_property / set_property names: circle 'Radius'; ellipse 'RadiusX' / 'RadiusY'; text 'Text' / 'Height'; survey point 'PointNumber' / 'Elevation'.

9.2.4.3. Surface

A TIN surface created by Drawing.create_surface (or looked up via find_surface / surfaces).

Member

Signature

Returns

Description

name

property (read-only)

str

Surface name.

handle

property (read-only)

str

Persistent handle.

elevation_at

elevation_at(x, y)

Optional[float]

Interpolated Z, or None outside the TIN.

add_point

add_point(x, y, z)

None

Insert a vertex and rebuild the TIN.

profile_along

profile_along(pts)

List[Tuple[float, float]]

Long-section: list of (station, z).

volume_to

volume_to(other, cell=1.0)

Tuple[float, float]

Grid-approx (cut, fill); cut where self is above other.

9.2.4.4. Alignment

A horizontal alignment created by create_alignment_from_polyline (or looked up via find_alignment / alignments).

Member

Signature

Returns

Description

name

property (read-only)

str

Alignment name.

handle

property (read-only)

str

Persistent handle.

length

property (read-only)

float

Total length.

start_station

property (read-only)

float

Start chainage.

end_station

property (read-only)

float

End chainage.

point_at_station

point_at_station(s)

Tuple[float, float, float]

(x, y, z) at chainage s.

direction_at_station

direction_at_station(s)

float

Bearing at s, in radians.

elevation_at_station

elevation_at_station(s)

float

Profile elevation at s.

station_offset_at

station_offset_at(x, y)

Tuple[float, float]

(station, offset); offset - left / + right.

elements

elements()

List[Dict[str, object]]

Geometry elements; keys kind / length / start_station / radius.

9.2.4.5. Module-level functions

The flat helpers and the live active_drawing attribute.

Member

Signature

Returns

Description

active_drawing

attribute (read-only)

Drawing

The current drawing; resolves fresh on each access.

cmd

cmd(s)

None

Run a command-line string.

write_line

write_line(s)

None

Print a line to the command line.

point

point(x, y, z=...)

Tuple[float, float, float]

Build a point tuple.

add_line

add_line(a, b)

str

Flat line; returns a handle string.

add_circle

add_circle(c, r)

str

Flat circle; returns a handle string.

add_text

add_text(pos, text, height)

str

Flat text; returns a handle string.

add_arc

add_arc(start, mid, end)

str

Flat arc; returns a handle string.

add_ellipse

add_ellipse(center, radx, rady, rotation=0.0)

str

Flat ellipse; returns a handle string.

add_rectangle

add_rectangle(c1, c2)

str

Flat rectangle; returns a handle string.

add_hatch

add_hatch(points, pattern="ANSI31", angle=0.0, scale=1.0)

str

Flat hatch; returns a handle string.

add_tunnel_section_line

add_tunnel_section_line(points, closed=False)

str

Flat tunnel section line; returns a handle string.

create_layer

create_layer(name, color=...)

bool

Create a layer (ACI color).

set_active_layer

set_active_layer(name)

None

Set the current layer.

zoom_extents

zoom_extents()

None

Zoom to drawing extents.

unregister_command

unregister_command(name)

None

Remove a registered custom command.

on

on(event, func)

Callable

Attach an event handler by name.

off

off(event, func)

None

Detach an event handler.

yield_

yield_()

None

Pump the UI; raises KeyboardInterrupt on Esc.

show_message

show_message(text)

None

Show a message box.

confirm

confirm(prompt)

bool

Yes/No prompt; True if confirmed.

input_string

input_string(prompt, default="")

Optional[str]

Text input; None if cancelled.

select_file

select_file(title="", filter="", save=False)

str

File chooser; '' if cancelled.

9.2.5. Decorators and events

9.2.5.1. Custom commands

Decorate a function with @iqcad.command to register a new IQcad command. Load it with PYLOAD (or drop it in the autoload folder) and it can be typed at the command prompt like any built-in.

@iqcad.command("NAME", aliases="N", category="Tools",
               description="What it does")
def handler(*args):
    ...

Warning

Command arguments arrive as strings. Cast them yourself (int(args[0]), float(args[1])).

Remove a registration with iqcad.unregister_command("NAME").

9.2.5.2. Events

Three event decorators register handlers that fire when the drawing changes:

Decorator

Handler receives

Fires when

@iqcad.on_entity_created

fn(entity) – an Entity

An entity is created.

@iqcad.on_entity_deleted

fn(handle) – a handle str

An entity is deleted.

@iqcad.on_selection_changed

fn(drawing) – the Drawing

The selection changes.

Handlers can also be attached and detached explicitly with iqcad.on(event, func) and iqcad.off(event, func).

Note

Handlers run synchronously on the GUI thread. An exception inside a handler is logged to the command line, not fatal. There is no on_entity_modified and no drawing-lifecycle or command events yet.

9.2.6. Dialogs

Four module-level dialog functions prompt the user. They are shared with the Pascal host (see Pascal Scripting). Run headlessly – or wherever no GUI provider is installed – they return safe defaults so an unattended script never blocks: confirm returns False, input_string returns None, and select_file returns ''.

Function

Signature

Returns

Description

show_message

show_message(text)

None

Show a message box with text.

confirm

confirm(prompt)

bool

Yes/No prompt; True if the user confirmed.

input_string

input_string(prompt, default="")

Optional[str]

Single-line text input; None if cancelled, else the string.

select_file

select_file(title="", filter="", save=False)

str

File chooser; '' if cancelled. filter is a pipe-delimited Caption|Mask (e.g. "CSV files|*.csv"); save selects an Open vs Save dialog.

import iqcad

iqcad.show_message("Starting import.")
if iqcad.confirm("Proceed?"):
    name = iqcad.input_string("Layer name", "Design")
    if name is not None:
        path = iqcad.select_file("Open points file", "CSV files|*.csv", False)
        if path:
            iqcad.write_line("import %s onto %s" % (path, name))

9.2.7. Examples

Worked example scripts ship under samples\python\:

  • samples\python\01_primitives.py – drawing geometry with the API.

  • samples\python\02_command.py – registering a custom command.

  • samples\python\03_events.py – wiring up event handlers.

  • samples\python\04_surface.py – TIN surfaces: create, query, profile, volume.

  • samples\python\05_alignment.py – alignments from a polyline.

  • samples\python\06_civil_workflow.py – chainage along an alignment while sampling a surface (the highlight).

9.2.7.1. Surfaces

Create a TIN from seed points, interpolate it, take a long-section, then compare two surfaces by volume (04_surface.py):

import iqcad

d = iqcad.active_drawing

eg = d.create_surface("EG", [
    (0, 0, 10.0), (50, 0, 11.0), (50, 40, 12.5),
    (0, 40, 10.5), (25, 20, 11.2),
])

z = eg.elevation_at(25, 20)            # float, or None outside the TIN
eg.add_point(25, 40, 11.8)             # rebuilds the TIN

for station, pz in eg.profile_along([(0, 20), (50, 20)]):
    iqcad.write_line("STA %.3f  Z %.3f" % (station, pz))

fg = d.create_surface("FG", [
    (0, 0, 11.0), (50, 0, 12.0), (50, 40, 13.5),
    (0, 40, 11.5), (25, 20, 12.2),
])
cut, fill = eg.volume_to(fg, cell=1.0) # cut where EG is above FG
iqcad.write_line("cut=%.2f  fill=%.2f" % (cut, fill))

9.2.7.2. Alignments

Build an alignment from a polyline (fillet radius required for 3+ vertices), then query it (05_alignment.py):

import iqcad

d = iqcad.active_drawing

# add_polyline is OO-only; capture the handle for the alignment factory.
ph = d.add_polyline([(0, 0), (100, 0), (100, 50), (160, 50)], False).handle
al = d.create_alignment_from_polyline(ph, 20.0, "CL-1")

iqcad.write_line("len=%.3f  STA %.3f..%.3f" % (
    al.length, al.start_station, al.end_station))

x, y, z = al.point_at_station(50.0)
d_rad = al.direction_at_station(50.0)
s, off = al.station_offset_at(50.0, 5.0)   # offset -left / +right

for el in al.elements():
    iqcad.write_line("%s len=%.3f R=%.3f" % (
        el["kind"], el["length"], el["radius"]))

9.2.7.3. Combined civil workflow

The highlight: step chainage along an alignment and sample the ground surface at each station (06_civil_workflow.py):

import iqcad

d = iqcad.active_drawing

surf = d.create_surface("EG", [
    (-20, -20, 10.0), (180, -20, 14.0), (180, 80, 16.0),
    (-20, 80, 11.0), (80, 25, 13.0),
])

ph = d.add_polyline([(0, 0), (100, 0), (100, 50), (160, 50)], False).handle
al = d.create_alignment_from_polyline(ph, 20.0, "CL-1")

step = 10.0
s = al.start_station
while s <= al.end_station + 1e-6:
    x, y, z = al.point_at_station(s)
    gz = surf.elevation_at(x, y)           # None outside the TIN
    iqcad.write_line("STA %.1f  surf=%.3f" % (
        s, gz if gz is not None else float('nan')))
    s += step

9.2.8. Dialogs and message boxes

Scripts can pop a parameter dialog or a message box through the same host-built UI the plugin SDK uses (so there is no Tk/Qt dependency).

iqcad.create_dialog(title) returns a dialog object; describe fields, execute(), then read the values:

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)   # min=0, max=0 → unbounded
dlg.add_text ("layer", "Layer name", "SHAPES")
dlg.add_color("color", "Color", 0x00B400)              # 0x00BBGGRR
dlg.add_bool ("filled","Filled?", False)
if dlg.execute():
    shape  = dlg.get_choice("shape")   # selected index
    size   = dlg.get_float("size")
    layer  = dlg.get_text("layer")
    color  = dlg.get_color("color")
    filled = dlg.get_bool("filled")

Field builders: add_text, add_multiline, add_int(…, min=0, max=0), add_float(…, min=0.0, max=0.0, decimals=3), add_bool, add_choice(key, label, options_list, default_index=0), add_file(…, filter='', save=False), add_folder, add_color. Getters: get_text (also returns the selected choice’s text), get_int, get_float, get_bool, get_choice (index), get_color. execute() returns False if the user cancels.

iqcad.message_box(text, caption="", buttons=iqcad.MB_OK, icon=iqcad.MB_ICON_NONE) shows a message box and returns the pressed button. Buttons: MB_OK, MB_OKCANCEL, MB_YESNO, MB_YESNOCANCEL; icons: MB_ICON_NONE/INFO/WARNING/ERROR/QUESTION; results: MB_RESULT_OK/CANCEL/YES/NO (a dismissed box returns MB_RESULT_CANCEL).

if iqcad.message_box("Delete selection?", "Confirm",
                     iqcad.MB_YESNO, iqcad.MB_ICON_QUESTION) == iqcad.MB_RESULT_YES:
    pass   # … proceed with the delete

9.2.9. Live forms

Where a dialog is modal, a live form is a non-modal, host-owned tool window that stays open: you build controls, keep the handles, mutate them at runtime, and react to the user through a handler object. It is the scripting face of the plugin SDK’s live tool windows.

iqcad.create_form(title, handler=None) returns a live form. Build it with add_button, add_label, add_text, add_multiline, add_int, add_float, add_bool, add_choice, add_color, add_file, add_folder, add_list (plus begin_row / end_row, find_control), then show(). Each add_* returns a control object with get_* / set_* accessors, set_caption / set_enabled / set_visible, and the list ops append_line / clear / item_count / get_selected / set_selected. The form also offers hide(), close(), is_visible() and set_title().

The optional handler is any object whose on_button_clicked(key), on_value_changed(key) and on_form_closed() methods the host calls (on the main thread) when the user interacts with the form:

class Handler:
    def __init__(self, form):
        self.form = form
    def on_button_clicked(self, key):
        if key == "add":
            n = self.form.find_control("n").get_int()
            self.form.find_control("log").append_line("Added: %d" % n)
            self.form.find_control("status").set_text("Last added: %d" % n)

form = iqcad.create_form("Form demo")
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.handler = Handler(form)        # or pass handler= to create_form
form.show()

iqcad.create_panel(title, handler=None) is identical to create_form 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 builder, control objects and handler surface are exactly the same; only the container differs:

panel = iqcad.create_panel("Form demo", handler)   # docked twin of create_form

9.2.10. Ribbon & menu

iqcad.add_ribbon_button(group, command, caption, hint='', icon='', large=True) and iqcad.add_menu_item(submenu, command, caption) add GUI entries that run a registered command — a button in the Plugins ribbon tab and an item in the Plugins menu. Scripts appear under a single Scripts group/submenu; icon is an absolute path to a PNG (omit it for a generic icon). See Ribbon & menu contributions.

@iqcad.command("GREET")
def greet():
    iqcad.message_box("Hello!", "Greet", iqcad.MB_OK, iqcad.MB_ICON_INFO)

iqcad.add_ribbon_button("Demo", "GREET", "Greet", hint="Say hello", large=True)
iqcad.add_menu_item("Demo", "GREET", "Greet")

9.2.11. Limitations and tips

  • Command arguments are strings. @iqcad.command passes every argument as a str; cast to int / float in your function.

  • ``elevation_at`` returns None outside the TIN. Always guard the result before using it in arithmetic.

  • Alignment fillet radius. create_alignment_from_polyline with radius=0 (the default) builds straight tangents through every vertex, including polylines with 3 or more vertices. Pass a non-zero radius (at least 0.01) only if you want the interior corners filleted into arcs.

  • Offset sign. station_offset_at returns an offset that is negative to the left and positive to the right of the alignment direction.

  • Limited event coverage. Only entity-created, entity-deleted, and selection-changed events exist today. There is no on_entity_modified and no drawing-lifecycle or command events yet.

  • No background / async execution. Scripts run on the GUI thread; use iqcad.yield_() to keep long loops responsive and Esc-cancelable.