.. _python-reference: .. index:: single: Python Scripting Reference 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 :ref:`python-scripting` in chapter 18. This chapter is the API reference you reach for once you know the basics. .. index:: single: Intro 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. .. index:: single: Running Python Running Python -------------- Five command-line entry points drive the Python host: .. index:: single: PYSCRIPTS (command) .. list-table:: :header-rows: 1 :widths: 22 78 * - Command - Description * - ``PYTHON `` (alias ``PY``) - Run a ``.py`` file once. Anything it registers is discarded when it finishes. * - ``PYEVAL `` - Evaluate a single Python expression and print the result at the command line (e.g. ``PYEVAL iqcad.active_drawing.name``). * - ``PYLOAD `` - 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 `` - Forward arguments to ``pip`` running inside the bundled interpreter. .. warning:: ``PYPIP install `` 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``). .. index:: single: Autoload and trust single: Autoload single: trust 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. .. index:: single: Responsiveness and Esc single: Responsiveness 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. .. index:: single: Type stubs Type stubs ~~~~~~~~~~ For autocomplete and type checking, point your editor at the stub ``samples\python\iqcad.pyi`` or at the bundled interpreter in ``python\``. .. index:: single: The object model 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. .. code-block:: text 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``. .. index:: single: Class reference Class reference --------------- .. index:: single: Drawing Drawing ~~~~~~~ Obtained from ``iqcad.active_drawing``. The factory for entities, surfaces, and alignments. .. list-table:: :header-rows: 1 :widths: 30 34 18 40 * - 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. .. index:: single: Entity Entity ~~~~~~ A single drawing object returned by the OO ``add_*`` methods. .. list-table:: :header-rows: 1 :widths: 24 32 22 40 * - 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'``. .. index:: single: Surface Surface ~~~~~~~ A TIN surface created by ``Drawing.create_surface`` (or looked up via ``find_surface`` / ``surfaces``). .. list-table:: :header-rows: 1 :widths: 24 34 26 38 * - 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*. .. index:: single: Alignment Alignment ~~~~~~~~~ A horizontal alignment created by ``create_alignment_from_polyline`` (or looked up via ``find_alignment`` / ``alignments``). .. list-table:: :header-rows: 1 :widths: 26 30 26 40 * - 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``. .. index:: single: Module-level functions Module-level functions ~~~~~~~~~~~~~~~~~~~~~~~~ The flat helpers and the live ``active_drawing`` attribute. .. list-table:: :header-rows: 1 :widths: 30 36 16 40 * - 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. .. index:: single: Decorators and events single: Decorators single: events Decorators and events ---------------------- .. index:: single: Custom commands 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. .. code-block:: python @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")``. .. index:: single: Events Events ~~~~~~ Three event decorators register handlers that fire when the drawing changes: .. list-table:: :header-rows: 1 :widths: 34 30 36 * - 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. .. index:: single: Dialogs Dialogs ------- Four module-level dialog functions prompt the user. They are shared with the Pascal host (see :ref:`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 ``''``. .. list-table:: :header-rows: 1 :widths: 30 36 16 36 * - 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. .. code-block:: python 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)) 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). .. index:: single: Surfaces Surfaces ~~~~~~~~ Create a TIN from seed points, interpolate it, take a long-section, then compare two surfaces by volume (``04_surface.py``): .. code-block:: python 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)) .. index:: single: Alignments Alignments ~~~~~~~~~~ Build an alignment from a polyline (fillet radius required for 3+ vertices), then query it (``05_alignment.py``): .. code-block:: python 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"])) .. index:: single: Combined civil workflow Combined civil workflow ~~~~~~~~~~~~~~~~~~~~~~~~~ The highlight: step chainage along an alignment and sample the ground surface at each station (``06_civil_workflow.py``): .. code-block:: python 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 .. index:: single: Dialogs and message boxes single: Dialogs single: message boxes 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: .. code-block:: python dlg = iqcad.create_dialog("Draw a shape") dlg.add_choice("shape", "Shape", ["Rectangle", "Circle"], 0) dlg.add_float("size", "Size (m)", 10.0, decimals=2) # 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``). .. code-block:: python if iqcad.message_box("Delete selection?", "Confirm", iqcad.MB_YESNO, iqcad.MB_ICON_QUESTION) == iqcad.MB_RESULT_YES: pass # … proceed with the delete .. index:: single: Live forms 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 :ref:`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: .. code-block:: python 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 .. index:: single: Ribbon & menu single: Ribbon single: menu 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 :ref:`plugin-ribbon-menu`. .. code-block:: python @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") .. index:: single: Limitations and tips single: tips 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.