.. index:: single: Scripting Scripting ========= Scripting lets you replay a sequence of IQcad commands from a text file — useful for repetitive drawing setup, batch operations, demos, and recording sequences of inputs for later reuse. This chapter documents the ``.scr`` text-script support and the embedded **Python** scripting host that ship with IQcad today. A third backend — **Pascal** — is still planned and will gain its own section when it lands. For full programmability (variables, loops, functions, and a real drawing API) jump to :ref:`python-scripting` below. The ``.scr`` engine documented first is the simpler input-replay mechanism. .. note:: The SCRIPT (``.scr``) engine replays *user inputs* — command names, coordinates, and prompt responses — exactly as if you had typed them at the command line. It has no concept of variables, loops, or conditionals. For those, use the Python backend (:ref:`python-scripting`). Overview -------- A script is a UTF-8 text file with a ``.scr`` extension (``.txt`` also works) containing IQcad command-line inputs, one operation per logical step. Each script line — or each whitespace-separated token within a line — is fed to the dispatcher in turn, exactly as if you had typed it manually and pressed Enter. This means anything you can do interactively can be scripted, with a few documented exceptions in :ref:`script-limitations` below. .. index:: single: Running scripts Running scripts --------------- Three ways to invoke a script: - **Command** — type ``SCRIPT`` at the command line with no arguments to open a file picker filtered to ``*.scr`` / ``*.txt``. Or type ``SCRIPT `` to run a specific file directly. Relative paths resolve against the working directory; quote the path if it contains spaces (``SCRIPT "C:\Project Setup\boilerplate.scr"``). - **Alias** — ``SCR`` is the short form of ``SCRIPT``. - **Menu** — **Tools → Run Script…** opens the file picker. While the script runs, each executed line is echoed to the command line as ``[script] `` in grey so you can follow what's happening. A successful run ends with ``Script complete.`` in green. **Stopping a script.** Pressing **Esc** cancels both the currently running CAD command and the script. The script also stops automatically on the first unknown command or unhandled exception, reporting the line number: .. code-block:: text Script stopped at line 9 (dispatch failed). .. index:: single: File format File format ----------- Plain UTF-8 text: - Lines beginning with ``;`` are comments (leading whitespace allowed). - **Spaces inside a line act like Enter** — the line is split into whitespace-separated tokens and each token is fed to the dispatcher in turn. ``LINE 0,0 10,10`` is three inputs at once. This matches AutoCAD's script convention. - **A blank line acts like a right-click** — it finishes the active drawing tool (POLYLINE, LINE chain, SPLINE, etc.). At the empty command prompt with no tool running, a blank line is a no-op. - A double-quoted string (``"…"``) is one token even if it contains spaces — useful for file paths. The surrounding quotes are stripped. .. index:: single: Coordinate forms Coordinate forms ~~~~~~~~~~~~~~~~ A line of typed coordinates is fed to whatever drawing tool is currently active and treated as a synthetic mouse click at the parsed world point. Five forms are accepted; see :ref:`entering-coordinates` in chapter 10 for the full reference. Briefly: - ``X,Y`` — absolute 2D, Z inherits from the last point. - ``X,Y,Z`` — absolute 3D. - ``@dX,dY[,dZ]`` — relative to the last point. - ``@dist`` downloads from PyPI and therefore needs network access. Behind a TLS-intercepting proxy or antivirus, the SSL handshake may fail. In that case 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``). .. index:: single: The iqcad module The ``iqcad`` module ~~~~~~~~~~~~~~~~~~~~~ Every script runs with a built-in ``iqcad`` module already imported. It exposes an object-oriented drawing API rooted at ``iqcad.active_drawing`` (a ``Drawing``), plus a set of flat convenience helpers. .. code-block:: python import iqcad dwg = iqcad.active_drawing # the current Drawing dwg.create_layer("Design", color=5) dwg.active_layer = "Design" h = dwg.add_line((0, 0), (10, 0)) # returns an Entity c = dwg.add_circle((5, 0), 2.5) h.color = 1 # AutoCAD colour index (1 = red) h.layer = "Design" print(h.handle, h.type_name, h.points) print(h.get_property("length")) dwg.zoom_extents() dwg.invalidate() ``Drawing`` offers ``add_line``, ``add_circle``, ``add_arc``, ``add_ellipse``, ``add_rectangle``, ``add_polyline``, ``add_text``, ``add_hatch``, ``add_survey_point``, ``add_tunnel_section_line``, ``create_layer``, ``find_by_handle``, ``entities``, ``selection``, ``zoom_extents``, ``invalidate``, and the properties ``active_layer``, ``name``, ``layer_names``. Creatable entities: line, circle, arc, ellipse, rectangle, polyline, text, hatch, survey point, tunnel-section-line. Each ``Entity`` exposes the properties ``handle``, ``type_name``, ``layer``, ``color``, ``points`` and the methods ``delete``, ``get_property``, ``set_property``. The same operations are also available as **flat helpers** for quick one-liners — ``iqcad.cmd``, ``iqcad.write_line``, ``iqcad.point``, ``iqcad.add_line`` / ``add_circle`` / ``add_arc`` / ``add_ellipse`` / ``add_rectangle`` / ``add_text`` / ``add_hatch`` / ``add_tunnel_section_line`` (each returns a handle string), ``iqcad.create_layer``, ``iqcad.set_active_layer``, ``iqcad.zoom_extents``: .. code-block:: python import iqcad iqcad.create_layer("Grid", color=8) iqcad.set_active_layer("Grid") for x in range(0, 101, 10): iqcad.add_line((x, 0), (x, 100)) iqcad.zoom_extents() .. index:: single: Surfaces Surfaces ~~~~~~~~ ``Drawing`` exposes three surface methods: ``create_surface`` builds a new TIN from a list of 3-D seed points, ``find_surface`` looks up an existing surface by name (returns ``None`` if not found), and ``surfaces`` enumerates all surfaces in the drawing. Each ``Surface`` has read-only ``name`` and ``handle`` properties and four methods: - ``elevation_at(x, y)`` — interpolates the TIN at the given plan coordinates; returns ``None`` when the point lies outside the triangulation. - ``add_point(x, y, z)`` — inserts a new vertex and rebuilds the TIN. - ``profile_along(pts)`` — samples the surface along a polyline defined by 2-D plan points; returns a list of ``(station, z)`` tuples. - ``volume_to(other, cell=1.0)`` — computes cut and fill volumes between this surface and *other* using a grid cell size of *cell* metres; returns ``(cut, fill)`` as a 2-tuple of floats. .. code-block:: python import iqcad d = iqcad.active_drawing # Build an existing-ground surface from survey points eg = d.create_surface("EG", [(0, 0, 10), (10, 0, 11), (5, 8, 10.5)]) z = eg.elevation_at(4, 2) # float, or None if outside the TIN prof = eg.profile_along([(0, 0), (50, 0)]) # [(station, z), ...] # Compare against a finished-grade surface already in the drawing fg = d.find_surface("FG") if fg is not None: cut, fill = eg.volume_to(fg) iqcad.write_line(f"cut={cut:.1f} fill={fill:.1f}") .. index:: single: Alignments Alignments ~~~~~~~~~~ ``Drawing`` exposes three alignment methods: ``create_alignment_from_polyline`` builds a new alignment from an existing polyline entity (located by handle), ``find_alignment`` looks up an alignment by name (returns ``None`` if not found), and ``alignments`` enumerates every alignment in the drawing. Each ``Alignment`` has read-only ``name``, ``handle``, ``length``, ``start_station``, and ``end_station`` properties and five methods: - ``point_at_station(s)`` — returns the ``(x, y, z)`` world point at chainage *s*. - ``direction_at_station(s)`` — bearing at chainage *s*, in radians. - ``elevation_at_station(s)`` — profile elevation at chainage *s* (from the attached vertical alignment, if any). - ``station_offset_at(x, y)`` — nearest chainage and signed offset for a plan point; offset is negative to the left and positive to the right of the alignment direction. - ``elements()`` — list of dicts describing the horizontal geometry elements; each dict has keys ``kind`` (``"line"`` or ``"arc"``), ``length``, ``start_station``, and ``radius`` (``0`` for tangent runs). .. code-block:: python import iqcad d = iqcad.active_drawing # Create an alignment from a polyline already in the drawing. # radius=0 builds straight tangents through every vertex; a non-zero # radius fillets the interior corners with curves instead. al = d.create_alignment_from_polyline(poly_handle, radius=25.0, name="CL-1") s, off = al.station_offset_at(x, y) # chainage + offset (-left / +right) x, y, z = al.point_at_station(120.0) z = al.elevation_at_station(120.0) # from the profile for el in al.elements(): print(el["kind"], el["length"]) # Iterate all alignments in the drawing for a in d.alignments(): print(a.name, f"{a.length:.1f} m") .. note:: ``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* (≥ 0.01) only if you want the interior corners filleted into curves. Full signature:: create_alignment_from_polyline(handle, radius=0, name="", use_z=False, vcurve_len=0) ``use_z=True`` builds the vertical profile from the polyline's per-vertex elevations — the scripted equivalent of accepting the ``AFP`` command's ``Create vertical profile from vertex elevations?`` prompt. ``vcurve_len`` sets the vertical curve length applied to every interior PVI (``0``, the default, keeps an angular profile). The PascalScript equivalent is ``CreateAlignmentFromPolylineZ(polyHandle, radius, name, vcurveLen)`` (see :ref:`pascal-scripting`), which always builds the profile from vertex elevations. .. index:: single: Custom commands Custom commands ~~~~~~~~~~~~~~~ Decorate a function with ``@iqcad.command`` to register a new IQcad command. Once registered (load it with ``PYLOAD`` or drop it in the autoload folder) it appears in ``HELP`` and can be typed at the command prompt like any built-in. **Command arguments arrive as strings** — cast them yourself. .. code-block:: python import iqcad @iqcad.command("GRID", aliases="GR", category="Drafting", description="Draw an N x N construction grid") def grid(n, spacing): n = int(n) # args are strings — cast them step = float(spacing) for i in range(n + 1): iqcad.add_line((0, i * step), (n * step, i * step)) iqcad.add_line((i * step, 0), (i * step, n * step)) iqcad.zoom_extents() Then at the prompt: ``GRID 5 10``. .. index:: single: Events Events ~~~~~~ Register handlers that fire when the drawing changes. Handlers run **synchronously** on the main thread; an exception in a handler is logged to the command line, not fatal. .. code-block:: python import iqcad @iqcad.on_entity_created def created(entity): iqcad.write_line(f"created {entity.type_name} {entity.handle}") @iqcad.on_entity_deleted def deleted(entity): iqcad.write_line(f"deleted {entity.handle}") @iqcad.on_selection_changed def selected(entities): iqcad.write_line(f"selection: {len(entities)} object(s)") Handlers can also be attached and detached explicitly with ``iqcad.on(event, func)`` and ``iqcad.off(event, func)``. .. index:: single: Long-running scripts Long-running scripts ~~~~~~~~~~~~~~~~~~~~~ A tight Python loop blocks IQcad's 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 (which raises ``KeyboardInterrupt`` in your code). .. code-block:: python import iqcad try: for i in range(100000): iqcad.add_circle((i, 0), 1) if i % 100 == 0: iqcad.yield_() # note the trailing underscore except KeyboardInterrupt: iqcad.write_line("cancelled by user") .. note:: The method is spelled ``iqcad.yield_()`` with a trailing underscore, because ``yield`` is a reserved keyword in Python. .. index:: single: Installed extensions (autoload) single: trust Installed extensions (autoload) and trust ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Any ``.py`` file dropped in ``%APPDATA%\IQcad\scripts\`` is loaded automatically at startup — the place to keep your personal commands and event handlers. 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 contents change). The dialog shows the file's SHA-256 hash and 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. .. index:: single: Type stubs and IDE support single: Type stubs single: IDE support Type stubs and IDE support ~~~~~~~~~~~~~~~~~~~~~~~~~~~ For autocomplete and type checking in your editor, point it at the type stub ``samples\python\iqcad.pyi`` (or at the bundled interpreter in ``python\``). Worked examples ship alongside it: - ``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. .. index:: single: Known limitations (Python) Known limitations (Python) ~~~~~~~~~~~~~~~~~~~~~~~~~~~ - **Command arguments are strings.** ``@iqcad.command`` passes every argument as a ``str``; cast to ``int`` / ``float`` in your function. - **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 UI thread; there is no ``iqcad.background()`` worker API yet. Use ``iqcad.yield_()`` to keep long loops responsive. .. note:: The Greek edition of this manual does not yet include the Python scripting section; its translation is a follow-up. .. index:: single: Future scripting languages Future scripting languages -------------------------- The SCR engine is intentionally minimal — it's a faithful replay of typed inputs. For richer scripting: - **Python** — *available now.* A Python 3.12 interpreter embedded in IQcad with bindings to the drawing API, command dispatcher, and entity model, supporting full control flow, computations, user-defined functions, and external libraries. See :ref:`python-scripting` above. - **Pascal** — *planned.* A Free Pascal script host using the same compiler that builds IQcad, for users who already write extensions in Pascal and want the same syntax for scripting. When it lands, this chapter will gain a *Pascal scripting* section.