7.4. Πρόσθετα (Native SDK)

Το IQcad μπορεί να επεκταθεί με δύο συμπληρωματικούς τρόπους. Τα σενάρια (Python ή Pascal, κεφάλαια Σενάρια Python, Αναφορά σεναρίων Python και Σενάρια Pascal) εκτελούν διερμηνευόμενο κώδικα μέσα στο IQcad και είναι ιδανικά για αυτοματοποίηση, ομαδικές εργασίες και εργαλεία μίας χρήσης. Τα πρόσθετα (plugins) είναι εγγενείς μεταγλωττισμένες βιβλιοθήκες (.dll / .so / .dylib) που το IQcad φορτώνει κατά την εκκίνηση· εκτελούνται με πλήρη ταχύτητα μηχανής, μπορούν να διανεμηθούν ως δυαδικά αρχεία χωρίς να αποκαλύπτεται ο πηγαίος κώδικας, και προσθέτουν εντολές, λειτουργίες σχεδίασης και χειριστές συμβάντων πρώτης κατηγορίας, ακριβώς όπως οι ενσωματωμένες λειτουργίες.

Αυτό το κεφάλαιο είναι η πλήρης αναφορά του εγγενούς SDK προσθέτων: ο σχεδιασμός και το ABI, το μοντέλο εμπιστοσύνης και ο Διαχειριστής Προσθέτων, η μορφή του manifest, δύο βήμα-προς-βήμα οδηγοί (Pascal και C), τα συνοδευτικά δείγματα και η πλήρης αναφορά διεπαφών.

Σημείωση

Η σύμβαση των προσθέτων είναι κοινή με τους hosts σεναρίων — ένα πρόσθετο και ένα σενάριο Pascal/Python βλέπουν το ίδιο μοντέλο σχεδίου, στρώσεων, οντοτήτων, επιφανειών και χαράξεων. Αν χρειάζεστε μόνο αυτοματοποίηση, τα σενάρια είναι απλούστερα· καταφύγετε σε πρόσθετο όταν χρειάζεστε εγγενή ταχύτητα, διανομή σε δυαδική μορφή ή όταν διανέμετε ένα εργαλείο σε άλλους χρήστες.

7.4.1. Πρόσθετα έναντι σεναρίων

Πτυχή

Σενάρια (Python / Pascal)

Εγγενές πρόσθετο

Γλώσσα

Python 3.12 ή PascalScript

Οποιαδήποτε γλώσσα μπορεί να παράγει κοινόχρηστη βιβλιοθήκη C-ABI (Free Pascal, C, C++, Rust, …)

Διανομή

Πηγαίο κείμενο (.py / .pas)

Μεταγλωττισμένο δυαδικό + ένα μικρό manifest JSON

Ταχύτητα

Διερμηνευόμενα

Εγγενής κώδικας μηχανής

Φόρτωση

Εκτέλεση κατ” απαίτηση από τον επεξεργαστή ή τη γραμμή εντολών

Εντοπισμός και φόρτωση κατά την εκκίνηση (αφού ενεργοποιηθεί)

Εμπιστοσύνη

Περίπου απομονωμένα· εκτελούνται εντός της διεργασίας

Πλήρης εμπιστοσύνη διεργασίας — πρέπει να ενεργοποιηθεί με συναίνεση

API

Αντικείμενα host IScript*

Διεπαφές IIq* (ένα λεπτό, παγωμένο στρώμα ABI πάνω στην ίδια μηχανή)

Και τα δύο μοιράζονται την ίδια υποκείμενη μηχανή, οπότε έννοιες όπως οι εντολές, τα συμβάντα, οι στρώσεις και το μοντέλο πολιτικού μηχανικού συμπεριφέρονται πανομοιότυπα.

7.4.2. Αρχιτεκτονική και σχεδιασμός

7.4.2.1. Η σύμβαση

Ό,τι μπορεί να δει ή να κάνει ένα πρόσθετο εκφράζεται μέσω ενός unit Pascal και του χειροκίνητα συντηρούμενου αντιγράφου του σε C:

  • cad/plugins/uIqSdk.pas — η σύμβαση: κάθε διεπαφή IIq*, οι κωδικοί αποτελέσματος, οι εγγραφές (records) και ο τύπος του σημείου εισόδου. Αυτό το αρχείο είναι η μοναδική πηγή αλήθειας.

  • cad/plugins/uIqSdkRtl.pas — μικρές βοηθητικές ρουτίνες χρόνου εκτέλεσης που χρησιμοποιεί ένα πρόσθετο Pascal για να κατασκευάσει συμβολοσειρές και απαριθμητές (IqStr, IqRGB, IqEnumStrings …).

  • cad/plugins/csdk/iqcad_sdk.h — η κεφαλίδα C/C++, ένα πιστό (1:1) αντίγραφο της σύμβασης για πρόσθετα γραμμένα σε C ή C++.

Ένα πρόσθετο δεν συνδέεται ποτέ (link) με το IQcad. Απλώς κάνει uses uIqSdk (Pascal) ή #include "iqcad_sdk.h" (C), εξάγει μία συνάρτηση και επικοινωνεί με τον host αποκλειστικά μέσω δεικτών διεπαφών που του παραδίδονται κατά τη φόρτωση.

7.4.2.2. Το ABI διεπαφών vtable

Το SDK χρησιμοποιεί ένα ABI vtable τύπου COM:

  • Κάθε αντικείμενο είναι μια διεπαφή που παράγεται από την IInterface (IUnknown): οι τρεις πρώτες θέσεις είναι QueryInterface, AddRef, Release. Τα αντικείμενα έχουν μέτρηση αναφορών· το τελευταίο Release τα αποδεσμεύει.

  • Κάθε μέθοδος χρησιμοποιεί τη σύμβαση κλήσης C (cdecl). Αυτή είναι η μοναδική σύμβαση κοινή και στους τρεις υποστηριζόμενους 64-bit στόχους, οπότε ένας host Pascal και ένα πρόσθετο C (ή το αντίστροφο) συνεργάζονται χωρίς ενδιάμεσο κώδικα.

  • Κανένας διαχειριζόμενος τύπος δεν διασχίζει ποτέ το όριο. Δεν υπάρχουν παράμετροι Pascal string/array/Variant στη σύμβαση. Τα δεδομένα μεταβλητού μήκους εκτίθενται ως διεπαφές με μέτρηση αναφορών (IIqString, IIqEnumerator)· τα δεδομένα σταθερού μεγέθους είναι απλές εγγραφές (TIqPoint3D, TIqColor, TIqVariant). Το κείμενο είναι πάντα UTF-16 (PWideChar / IIqString).

Σημείωση

Δεν εμπλέκεται κανένα runtime Windows COM — ούτε CoCreateInstance, ούτε μητρώο, ούτε apartments. Το «τύπου COM» εδώ σημαίνει μόνο τη δυαδική διάταξη: ένας δείκτης vtable ακολουθούμενος από μεθόδους cdecl, με τα QueryInterface/AddRef/Release στην αρχή. Αυτό λειτουργεί πανομοιότυπα σε Windows, Linux και macOS, συμπεριλαμβανομένου του Apple silicon.

7.4.2.3. Η σύμβαση παραμέτρων εξόδου

Μια συνάρτηση που επιστρέφει τύπο τιμής (ένα Double, ένα TIqColor, ένα LongInt) τον επιστρέφει απευθείας:

function Color: TIqColor; cdecl;
function ElevationAt(const X, Y: Double; out Z: Double): TIqResult; cdecl;

Μια συνάρτηση που επιστρέφει διεπαφή ή συμβολοσειρά την επιστρέφει μέσω παραμέτρου out και αποδίδει αντί αυτού έναν κωδικό αποτελέσματος:

function ActiveDrawing(out D: IIqDrawing): TIqResult; cdecl;
function Name(out S: IIqString): TIqResult; cdecl;

Αυτό είναι υποχρεωτικό για την ασφάλεια του ABI: η επιστροφή μιας διεπαφής με μέτρηση αναφορών ως αποτέλεσμα συνάρτησης δεν είναι συμβατή με το C-ABI στο όριο Free Pascal / C. Ελέγχετε πάντα και τα δύο, τον κωδικό αποτελέσματος και την παράμετρο εξόδου — για παράδειγμα η ActiveDrawing επιστρέφει IQ_OK με D = nil όταν δεν είναι ανοιχτό κανένα σχέδιο.

7.4.2.4. Παγωμένες διεπαφές και εκδόσεις

Μόλις μια διεπαφή κυκλοφορήσει, η διάταξή της δεν αλλάζει ποτέ — οι μέθοδοι δεν αναδιατάσσονται, δεν αφαιρούνται και δεν αλλάζουν υπογραφή. Η σύμβαση εξελίσσεται μόνο με:

  • προσθήκη μεθόδων στο τέλος μιας διεπαφής σε νέα δευτερεύουσα έκδοση (τα υπάρχοντα πρόσθετα συνεχίζουν να λειτουργούν — απλώς δεν καλούν τις νέες θέσεις), ή

  • προσθήκη μιας εντελώς νέας διεπαφής με νέο GUID (IIqDrawing2 …) όταν μια ασύμβατη αλλαγή είναι αναπόφευκτη.

Δύο σταθερές στο uIqSdk.pas το παρακολουθούν αυτό:

  • IQSDK_API_MAJOR — αυξάνεται μόνο σε ασύμβατη αλλαγή.

  • IQSDK_API_MINOR — αυξάνεται σε κάθε προσθετική αλλαγή.

Ένα πρόσθετο δηλώνει την ελάχιστη δευτερεύουσα έκδοση που χρειάζεται στο manifest του (apiVersion.min_minor)· ο φορτωτής το δέχεται μόνο όταν host.major = manifest.major και host.minor >= manifest.min_minor (δείτε Το manifest (plugin.json)). Η τρέχουσα σύμβαση είναι η API 1.6.

Προειδοποίηση

Όσο το SDK βρίσκεται σε έκδοση πριν την 1.0, μπορεί ακόμη να αλλάζει επί τόπου μεταξύ εκδόσεων του IQcad. Ένα πρόσθετο μεταγλωττισμένο με παλαιότερη σύμβαση μπορεί να προκαλέσει κατάρρευση ενός νεότερου IQcad. Μεταγλωττίζετε ξανά τα πρόσθετά σας από το τρέχον SDK κάθε φορά που ενημερώνετε το IQcad, μέχρι η σύμβαση να δηλωθεί σταθερή.

7.4.2.5. Νήματα εκτέλεσης

Το μοντέλο σχεδίου είναι μονονηματικό. Όλα τα σημεία εισόδου του προσθέτου (Invoke εντολών, callbacks συμβάντων) καλούνται στο κύριο νήμα του IQcad, και πρέπει να αγγίζετε το σχέδιο μόνο από αυτό το νήμα. Αν ξεκινήσετε ένα νήμα εργασίας, επιστρέψτε στο κύριο νήμα με:

Host.PostToMainThread(myRunnable);   // το myRunnable.Run εκτελείται στο κύριο νήμα

Η κλήση μεθόδων σχεδίου από άλλο νήμα επιστρέφει IQ_E_WRONGTHREAD ή προκαλεί απροσδιόριστη συμπεριφορά.

7.4.2.6. Μνήμη και διάρκεια ζωής

Τα αντικείμενα έχουν μέτρηση αναφορών. Στην Pascal αυτό είναι αυτόματο: εκχωρείτε μια μεταβλητή διεπαφής και ο μεταγλωττιστής παράγει τα AddRef/Release για εσάς. Στη C καλείτε μόνοι σας lpVtbl->AddRef / lpVtbl->Release (τα δείγματα χρησιμοποιούν στατικά singletons με διάρκεια ζωής ίση με τη βιβλιοθήκη, οπότε οι μέθοδοι μέτρησης αναφορών τους δεν κάνουν τίποτα).

Σημείωση

Η μονάδα του προσθέτου δεν αποφορτώνεται ποτέ όσο εκτελείται το IQcad. Όταν απενεργοποιείτε ένα πρόσθετο, ο φορτωτής το απενεργοποιεί (καταργεί την καταχώριση των εντολών του και απελευθερώνει τις αναφορές του) αλλά δεν καλεί dlclose / FreeLibrary στο δυαδικό αρχείο. Ο host μπορεί ακόμη να κρατά αντικείμενα με μέτρηση αναφορών των οποίων ο κώδικας βρίσκεται στη βιβλιοθήκη σας· η αποφόρτωσή της θα μετέτρεπε το επόμενο Release σε κατάρρευση. Η μονάδα παραμένει φορτωμένη στη μνήμη μέχρι να τερματιστεί το IQcad. Ένα απενεργοποιημένο πρόσθετο είναι πραγματικά αδρανές — οι εντολές του έχουν εξαφανιστεί — απλώς παραμένει στη μνήμη.

7.4.3. Ο κύκλος ζωής του προσθέτου

Ένα δυαδικό αρχείο προσθέτου εξάγει ακριβώς μία συνάρτηση. Η διάρκεια ζωής του είναι μια σταθερή ακολουθία που καθοδηγείται από τον φορτωτή:

discover  ──▶  load (dlopen)  ──▶  IqcadPluginInit(host, out plugin)
                                           │
                                           ▼
                                   plugin.Describe(out info)   ◀─ το id πρέπει να ταιριάζει με το manifest
                                           │
                                           ▼
                                   plugin.Activate(host)       ◀─ καταχώριση εντολών, εγγραφή σε συμβάντα
                                           │
                                    … εκτέλεση εντολών …
                                           │
                                           ▼
                                   plugin.Deactivate           ◀─ κατάργηση καταχώρισης, απελευθέρωση cookies
                                           │
                                           ▼
                                   απελευθέρωση αναφορών (η μονάδα παραμένει φορτωμένη)
  1. Discover (εντοπισμός) — κατά την εκκίνηση ο φορτωτής σαρώνει τον κατάλογο προσθέτων για υποφακέλους που περιέχουν plugin.json, διαβάζει κάθε manifest και ελέγχει την έκδοσή του.

  2. Load (φόρτωση) — για κάθε ενεργοποιημένο και εγκεκριμένο συμβατό πρόσθετο, ο φορτωτής κάνει dlopen στο δυαδικό αρχείο και αναζητά την εξαγόμενη συνάρτηση IqcadPluginInit.

  3. Init (αρχικοποίηση) — η IqcadPluginInit(host, out plugin) δημιουργεί το αντικείμενο του προσθέτου σας και το επιστρέφει. Το host που λαμβάνετε είναι η λαβή σας προς όλα.

  4. Describe (περιγραφή) — ο φορτωτής καλεί την plugin.Describe και επαληθεύει ότι το αναφερόμενο Id ταιριάζει με το manifest (μια αναντιστοιχία θέτει το πρόσθετο σε καραντίνα).

  5. Activate (ενεργοποίηση) — η plugin.Activate(host) είναι το σημείο όπου καταχωρίζετε εντολές και εγγράφεστε σε συμβάντα.

  6. Deactivate (απενεργοποίηση) — κατά τον τερματισμό ή όταν ο χρήστης απενεργοποιεί το πρόσθετο, εκτελείται η plugin.Deactivate· καταργήστε εδώ την καταχώριση των εντολών σας και απελευθερώστε τα cookies εγγραφής σε συμβάντα.

7.4.3.1. Καταστάσεις φορτωτή

Κάθε εντοπισμένο πρόσθετο έχει μια κατάσταση ορατή στον Διαχειριστή Προσθέτων:

Κατάσταση

Σημασία

Discovered

Το manifest διαβάστηκε, δεν έχει ελεγχθεί ακόμη.

Eligible

Συμβατό με αυτό το IQcad, δεν έχει φορτωθεί (ακόμη).

Incompatible

Δεν υπάρχει δυαδικό αρχείο για αυτή την πλατφόρμα ή υπάρχει αναντιστοιχία API — δεν φορτώνεται ποτέ.

Active

Φορτώθηκε και ενεργοποιήθηκε· οι εντολές του είναι διαθέσιμες.

Failed

Η φόρτωση ή η ενεργοποίηση προκάλεσε σφάλμα — σε καραντίνα, με το σφάλμα καταγεγραμμένο.

7.4.4. Εμπιστοσύνη και Διαχειριστής Προσθέτων

Ένα εγγενές πρόσθετο εκτελείται με τα πλήρη δικαιώματα του IQcad: μπορεί να διαβάζει και να γράφει αρχεία, να ανοίγει συνδέσεις δικτύου, και ένα ελαττωματικό πρόσθετο μπορεί να προκαλέσει κατάρρευση της εφαρμογής. Γι” αυτό το IQcad θεωρεί τα πρόσθετα μη έμπιστα μέχρι να δηλώσετε το αντίθετο.

Τα πρόσθετα είναι απενεργοποιημένα από προεπιλογή. Ένα πρόσθετο που μόλις αντιγράψατε εντοπίζεται και εμφανίζεται στη λίστα, αλλά δεν φορτώνεται μέχρι να το ενεργοποιήσετε μία φορά και να αποδεχτείτε μια προτροπή εμπιστοσύνης. Η επιλογή σας απομνημονεύεται, οπότε το πρόσθετο φορτώνεται αυτόματα στις επόμενες εκκινήσεις.

7.4.4.1. Άνοιγμα του διαχειριστή

  • Εντολή — πληκτρολογήστε PLUGINS (ψευδώνυμο PLUGINMANAGER).

  • Κορδέλα — Tools → Scripting → Plugins.

  • Μενού — Tools → Plugin Manager…

Ο διάλογος παραθέτει κάθε εντοπισμένο πρόσθετο με τη σημαία Enabled, το όνομα, την έκδοση, τον εκδότη και την κατάσταση, καθώς και ένα παράθυρο λεπτομερειών που δείχνει το id, την απαιτούμενη έκδοση API, τη διαδρομή εγκατάστασης και το τελευταίο σφάλμα, αν υπάρχει.

7.4.4.2. Ενεργοποίηση, απενεργοποίηση, επαναφόρτωση

  • Enable — επιλέξτε ένα πρόσθετο και κάντε κλικ στο Enable. Την πρώτη φορά εμφανίζεται ένας διάλογος εμπιστοσύνης:

    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'?
    

    Κάντε κλικ στο Yes για να δώσετε συναίνεση (απομνημονεύεται) και να φορτώσετε το πρόσθετο. Οι εντολές του γίνονται αμέσως διαθέσιμες και ανακοινώνονται στη γραμμή εντολών.

  • Disable — απενεργοποιεί το πρόσθετο (οι εντολές του εξαφανίζονται) και καθαρίζει τη σημαία ενεργοποίησης ώστε να μη φορτωθεί αυτόματα την επόμενη φορά.

  • Reload — σαρώνει ξανά τον κατάλογο προσθέτων και συμφιλιώνει την κατάσταση· χρησιμοποιήστε το μετά την εγκατάσταση ή την εκ νέου μεταγλώττιση ενός προσθέτου χωρίς επανεκκίνηση του IQcad.

  • Open Folder — ανοίγει τον κατάλογο εγκατάστασης του προσθέτου (ή τον ριζικό κατάλογο προσθέτων) στη διαχείριση αρχείων σας.

7.4.4.3. Διατήρηση κατάστασης

Οι σημαίες enabled και consented κάθε προσθέτου αποθηκεύονται ανά id στο settings.json του IQcad και εφαρμόζονται ξανά κατά την εκκίνηση. Κατά την εκκίνηση το IQcad κάνει συμφιλίωση: φορτώνει μόνο πρόσθετα που είναι κατάλληλα και ενεργοποιημένα και εγκεκριμένα.

7.4.5. Εγκατάσταση προσθέτου

Τα πρόσθετα βρίσκονται σε έναν κατάλογο ανά χρήστη:

Πλατφόρμα

Κατάλογος προσθέτων

Linux / macOS

~/.config/IQcad/plugins/

Windows

%APPDATA%\IQcad\plugins\

Κάθε πρόσθετο έχει τον δικό του υποφάκελο που περιέχει το δυαδικό αρχείο και το manifest:

~/.config/IQcad/plugins/
    com.example.myfirst/
        plugin.json
        libMyFirstPlugin.so        (Linux)
        MyFirstPlugin.dll          (Windows)
        libMyFirstPlugin.dylib     (macOS)

Το όνομα του υποφακέλου είναι ελεύθερο — ο φορτωτής διαβάζει το πραγματικό id από το plugin.json. Αφού αντιγράψετε τα αρχεία, είτε επανεκκινήστε το IQcad είτε κάντε κλικ στο Reload στον Διαχειριστή Προσθέτων, και στη συνέχεια ενεργοποιήστε το πρόσθετο.

7.4.6. Το manifest (plugin.json)

Κάθε πρόσθετο συνοδεύεται από ένα plugin.json δίπλα στο δυαδικό του αρχείο. Αυτό ενημερώνει τον φορτωτή ποιο είναι το πρόσθετο, ποιο API του IQcad χρειάζεται και ποιο δυαδικό αρχείο να φορτώσει σε κάθε πλατφόρμα.

Πεδίο

Τύπος

Σημασία

schemaVersion

int

Έκδοση μορφής του manifest — προς το παρόν πάντα 1.

id

string

Υποχρεωτικό. Μοναδικό id σε μορφή reverse-DNS (π.χ. com.example.myfirst). Πρέπει να ταιριάζει με το id που επιστρέφει η Describe.

name

string

Όνομα εμφάνισης στον Διαχειριστή Προσθέτων.

version

string

Η έκδοση του ίδιου του προσθέτου (ενημερωτική, π.χ. "1.0.0").

apiVersion.major

int

Απαιτούμενη κύρια έκδοση SDK (πρέπει να ισούται με την κύρια έκδοση του host).

apiVersion.min_minor

int

Ελάχιστη δευτερεύουσα έκδοση SDK που χρειάζεται το πρόσθετο.

publisher

string

Προαιρετικό όνομα εκδότη.

description

string

Προαιρετική περιγραφή μίας γραμμής.

icon

string

Προαιρετικό όνομα αρχείου εικονιδίου (δεσμευμένο).

entry

object

Υποχρεωτικό. Αντιστοίχιση κλειδιού πλατφόρμας → όνομα αρχείου βιβλιοθήκης για τη συγκεκριμένη πλατφόρμα.

Τα αναγνωρισμένα κλειδιά πλατφόρμας είναι x86_64-win64, x86_64-linux και aarch64-darwin. Ένα πλήρες 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."
}

Προειδοποίηση

Το όνομα αρχείου κάτω από το entry πρέπει να ταιριάζει ακριβώς με το δυαδικό αρχείο που παράγετε για τη συγκεκριμένη πλατφόρμα. Ο φορτωτής κατασκευάζει τη διαδρομή ως φάκελος-προσθέτου + όνομα αρχείου entry και φορτώνει ακριβώς αυτό το αρχείο. Μια αναντιστοιχία (π.χ. το manifest λέει libMyFirstPlugin.so αλλά η μεταγλώττιση παρήγαγε MyFirstPlugin) είναι το πιο συνηθισμένο λάθος του τύπου «το πρόσθετό μου δεν φορτώνεται».

7.4.7. Οδηγός 1 — το πρώτο σας πρόσθετο (Free Pascal)

Αυτός ο οδηγός κατασκευάζει ένα πλήρες, λειτουργικό πρόσθετο που προσθέτει την εντολή HELLO. Είναι το μικρότερο χρήσιμο πρόσθετο και δείχνει κάθε απαιτούμενο κομμάτι.

7.4.7.1. Προαπαιτούμενα

  • Free Pascal / Lazarus (η ίδια εργαλειοθήκη με την οποία κατασκευάζεται το IQcad).

  • Το δέντρο πηγαίου κώδικα IQcad/ICAD, για τα units του SDK. Σημειώστε τη διαδρομή προς το cad/plugins/ — η διαδρομή αναζήτησης units του έργου σας πρέπει να δείχνει εκεί ώστε να επιλύονται τα uIqSdk και uIqSdkRtl.

7.4.7.2. Βήμα 1 — γράψτε τον πηγαίο κώδικα

Δημιουργήστε το MyFirstPlugin.lpr:

library MyFirstPlugin;

{$mode ObjFPC}{$H+}
{$interfaces COM}          // διεπαφές τύπου COM με μέτρηση αναφορών

uses
  Classes, SysUtils,
  uIqSdk, uIqSdkRtl;       // η σύμβαση του SDK + βοηθητικές ρουτίνες χρόνου εκτέλεσης

type
  { Η εντολή που εκτελεί ο χρήστης πληκτρολογώντας HELLO. Μια εντολή
    είναι απλώς ένα αντικείμενο που υλοποιεί την 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;

  { Το αντικείμενο του προσθέτου — δημιουργείται από την IqcadPluginInit,
    κατέχει την εντολή. }
  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;   // διαχωρισμένα με κόμμα για περισσότερα

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 αποδίδει τα ορίσματα της εντολής ως στοιχεία IIqString (κανένα εδώ).
  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');   // ΠΡΕΠΕΙ να ταιριάζει με το 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;

{ Το μοναδικό εξαγόμενο σύμβολο που αναζητά ο φορτωτής. }
function IqcadPluginInit(const Host: IIqHost; out Plugin: IIqPlugin): TIqResult; cdecl;
begin
  Plugin := TMyFirstPlugin.Create;
  Result := IQ_OK;
end;

exports
  IqcadPluginInit;

begin
end.

Αυτό είναι ολόκληρο το πρόσθετο. Προσέξτε τα τέσσερα απαιτούμενα συστατικά:

  1. {$interfaces COM} ώστε οι διεπαφές να έχουν μέτρηση αναφορών.

  2. Μία κλάση που υλοποιεί την IIqPlugin.

  3. Μία ή περισσότερες κλάσεις που υλοποιούν την IIqCommand.

  4. Η μοναδική εξαγόμενη IqcadPluginInit που επιστρέφει το πρόσθετο μέσω της παραμέτρου out.

7.4.7.3. Βήμα 2 — γράψτε το manifest

Δημιουργήστε το plugin.json δίπλα στον πηγαίο κώδικα (δείτε Το manifest (plugin.json) για όλα τα πεδία):

{
  "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"
  }
}

7.4.7.4. Βήμα 3 — μεταγλώττιση

Με ένα αρχείο έργου Lazarus (.lpi) του οποίου η διαδρομή other unit files περιλαμβάνει το cad/plugins, μεταγλωττίστε τη βιβλιοθήκη:

lazbuild MyFirstPlugin.lpi

ή απευθείας με τον μεταγλωττιστή, δείχνοντας με το -Fu τα units του SDK:

fpc -Fu/path/to/ICAD/cad/plugins MyFirstPlugin.lpr

Στο Linux αυτό παράγει το libMyFirstPlugin.so — ακριβώς το όνομα που αναμένει η καταχώριση x86_64-linux του manifest. Στα Windows μεταγλωττίστε ένα .dll, στο macOS ένα .dylib, το καθένα σύμφωνα με το αντίστοιχο όνομα αρχείου του entry.

7.4.7.5. Βήμα 4 — εγκατάσταση και ενεργοποίηση

Αντιγράψτε το δυαδικό αρχείο και το plugin.json σε έναν υποφάκελο του καταλόγου προσθέτων:

mkdir -p ~/.config/IQcad/plugins/com.example.myfirst
cp libMyFirstPlugin.so plugin.json ~/.config/IQcad/plugins/com.example.myfirst/

Ξεκινήστε το IQcad (ή κάντε κλικ στο Reload στον Διαχειριστή Προσθέτων), ανοίξτε τον διαχειριστή με PLUGINS, επιλέξτε My First Plugin, κάντε κλικ στο Enable και αποδεχτείτε την προτροπή εμπιστοσύνης. Η γραμμή εντολών επιβεβαιώνει:

Plugin command available: HELLO  (aliases: HI)

7.4.7.6. Βήμα 5 — εκτελέστε το

Πληκτρολογήστε HELLO (ή το ψευδώνυμο HI) στη γραμμή εντολών. Θα δείτε:

Hello from my first IQcad plugin!

Πληκτρολογήστε HELP και μετακινηθείτε στην ενότητα Loaded plugin commands για να δείτε την εντολή σας μαζί με τις εντολές άλλων προσθέτων.

7.4.7.7. Αντιμετώπιση προβλημάτων

  • Το πρόσθετο δεν εμφανίζεται στη λίστα — ο φάκελος πρέπει να περιέχει έγκυρο plugin.json με μη κενό id. Κάντε κλικ στο Reload.

  • Κατάσταση «Incompatible» — δεν υπάρχει entry για το κλειδί της πλατφόρμας σας ή η απαιτούμενη apiVersion είναι νεότερη από αυτό το IQcad. Ελέγξτε το παράθυρο λεπτομερειών.

  • Κατάσταση «Failed» — το δυαδικό αρχείο δεν μπόρεσε να φορτωθεί, λείπει η εξαγόμενη IqcadPluginInit ή το id της Describe δεν ταιριάζει με το manifest. Το ακριβές σφάλμα βρίσκεται στο παράθυρο λεπτομερειών και στο αρχείο καταγραφής αποσφαλμάτωσης.

  • Ενεργοποιημένο αλλά δεν συμβαίνει τίποτα — επιβεβαιώστε ότι η εντολή ανακοινώθηκε στη γραμμή εντολών· ελέγξτε το HELP.

7.4.8. Οδηγός 2 — σχεδίαση γεωμετρίας

Ένα πιο χρήσιμο πρόσθετο σχεδιάζει στο ενεργό σχέδιο. Αυτή η εντολή DRAWDEMO δημιουργεί μια στρώση και προσθέτει μερικές οντότητες. Μόνο το Invoke της εντολής διαφέρει από τον Οδηγό 1· όλα τα υπόλοιπα (η κλάση του προσθέτου, το manifest, η μεταγλώττιση) έχουν την ίδια μορφή.

function TDrawDemoCommand.Invoke(const Args: IIqEnumerator): TIqResult; cdecl;
var
  d: IIqDrawing;
  e: IIqEntity;
  a, b, c: TIqPoint3D;
begin
  // 1. Λήψη του ενεργού σχεδίου (ελέγξτε ΚΑΙ τον κωδικό ΚΑΙ την παράμετρο εξόδου).
  if (FHost.ActiveDrawing(d) <> IQ_OK) or (d = nil) then
  begin
    FHost.Log(illInfo, 'DRAWDEMO: open a drawing first');
    Exit(IQ_OK);
  end;

  // 2. Δημιουργία πράσινης στρώσης και ορισμός της ως τρέχουσας.
  //    Το TIqColor είναι πακεταρισμένο RGB 0x00BBGGRR — κατασκευάστε το με
  //    IqRGB, ΟΧΙ με δείκτη ACI. Το IqRGB(0,180,0) είναι πράσινο.
  d.CreateLayer('DEMO', IqRGB(0, 180, 0));
  d.SetActiveLayer('DEMO');

  // 3. Προσθήκη οντοτήτων. Τα σημεία είναι εγγραφές TIqPoint3D.
  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. Ανάγνωση κάποιας τιμής: η λαβή (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. Επανασχεδίαση ώστε η νέα γεωμετρία να εμφανιστεί αμέσως.
  d.Invalidate;
  Result := IQ_OK;
end;

Βασικά σημεία:

  • Η IIqDrawing προσεγγίζεται μέσω της Host.ActiveDrawing. Προσφέρει AddLine, AddCircle, AddArc, AddRectangle, AddText, AddHatch και άλλα (δείτε IIqDrawing).

  • Τα χρώματα είναι RGB, όχι δείκτες ACI. Χρησιμοποιήστε IqRGB(r, g, b) (Pascal) ή IQ_RGB(r, g, b) (C). Η μεταβίβαση ενός μικρού ακεραίου όπως το 3 δίνει RGB(3,0,0) — σχεδόν μαύρο — ένα κλασικό λάθος.

  • Καλέστε την Invalidate (και προαιρετικά την ZoomExtents) ώστε να ανανεωθεί η προβολή.

Οι εργασίες πολιτικού μηχανικού ακολουθούν το ίδιο μοτίβο: AddSurveyPoint, CreateSurface, IIqSurface.ElevationAt, ερωτήματα χαράξεων — δείτε Πολιτικός μηχανικός: IIqSurface, IIqProfilePoint, IIqAlignment και το δείγμα civilui.

7.4.9. Διάλογοι παραμέτρων και πλαίσια μηνυμάτων

Ένα πρόσθετο δεν μπορεί να δημιουργήσει δικές του φόρμες — το IQcad είναι εφαρμογή LCL και ένα δεύτερο στιγμιότυπο LCL μέσα στο πρόσθετο θα προκαλούσε κατάρρευση της διεργασίας (δείτε την εισαγωγή του Πρόσθετα (Native SDK)). Αντί γι” αυτό, ο host κατασκευάζει το περιβάλλον χρήστη: εσείς περιγράφετε έναν διάλογο και το IQcad δημιουργεί, εμφανίζει και διαβάζει πίσω κάθε στοιχείο ελέγχου.

7.4.9.1. Το εμπλουτισμένο πλαίσιο μηνύματος

Η IIqHostUI.MessageBox δέχεται μια λεζάντα, ένα σύνολο κουμπιών και ένα εικονίδιο, και επιστρέφει ποιο κουμπί πάτησε ο χρήστης:

var ui: IIqHostUI; ans: TIqMsgResult;
begin
  FHost.UI(ui);
  ui.MessageBox('Delete the selection?', 'Confirm', imbYesNo, imiQuestion, ans);
  if ans = imrYes then
    ...;   // συνέχεια
end;

Τα σύνολα κουμπιών είναι imbOK / imbOKCancel / imbYesNo / imbYesNoCancel· τα εικονίδια imiNone / imiInfo / imiWarning / imiError / imiQuestion· τα αποτελέσματα imrOK / imrCancel / imrYes / imrNo. Ένας διάλογος που απορρίφθηκε (ή η απουσία παρόχου) επιστρέφει imrCancel.

7.4.9.2. Διάλογος παραμέτρων

Κατασκευάστε μια φόρμα, εκτελέστε την, διαβάστε τις τιμές:

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 διαχωρίζει τις επιλογές
  dlg.AddFloat ('size',  'Size (m)', 10, 0, 0, 2);            // Min=Max=0 → χωρίς όρια, 2 δεκαδικά
  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);          // ο χρήστης ακύρωσε (ή headless)

  dlg.GetChoice('shape', shape);
  dlg.GetFloat ('size',  size);
  dlg.GetText  ('layer', layer);
  dlg.GetColor ('color', col);
  dlg.GetBool  ('filled', filled);
  // … δημιουργία της στρώσης και σχεδίαση του ορθογωνίου/κύκλου …
end;

Κάθε είδος πεδίου αντιστοιχεί στον φυσικό του επεξεργαστή (text → πεδίο επεξεργασίας, multiline → memo, int/float → spin, bool → checkbox, choice → αναπτυσσόμενη λίστα, file/folder → πεδίο διαδρομής με Browse, color → δείγμα χρώματος + επιλογέας χρώματος). Δείτε την πλήρη λίστα μεθόδων της IIqDialog στην αναφορά API, καθώς και το πλήρες λυμένο παράδειγμα στο δείγμα dialogdemo / cdialogdemo.

7.4.9.3. Οι ίδιοι διάλογοι από σενάρια

Η μηχανή διαλόγων είναι κοινή με τους hosts σεναρίων, οπότε τα σενάρια Python και Pascal αποκτούν τους ίδιους διαλόγους παραμέτρων και το ίδιο εμπλουτισμένο πλαίσιο μηνύματος.

Python (δείτε Αναφορά σεναρίων 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)
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 (δείτε Σενάρια Pascal):

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;

7.4.10. Ζωντανά παράθυρα εργαλείων

Ένας διάλογος παραμέτρων (Διάλογοι παραμέτρων και πλαίσια μηνυμάτων) είναι αποκλειστικός (modal): μπλοκάρει, ο χρήστης τον συμπληρώνει, η Execute επιστρέφει. Ένα ζωντανό παράθυρο εργαλείου είναι το αντίθετο — ένα μη αποκλειστικό παράθυρο, ιδιοκτησίας του host, που παραμένει ανοιχτό αφού επιστρέψει η εντολή. Το πρόσθετο κατασκευάζει στοιχεία ελέγχου, διατηρεί τις λαβές τους, τα τροποποιεί κατά την εκτέλεση και αντιδρά στον χρήστη μέσω ενός αντικειμένου callback. Έτσι κατασκευάζετε ένα πλευρικό πάνελ, ένα αρχείο καταγραφής σε εξέλιξη ή ένα διαδραστικό εργαλείο.

Όπως και με τους διαλόγους, ο host κατέχει κάθε στοιχείο ελέγχου — το πρόσθετο δεν αγγίζει ποτέ το LCL. Η IIqHostUI.CreateForm (API 1.4) επιστρέφει έναν κατασκευαστή IIqForm· κάθε Add* δημιουργεί ένα πραγματικό στοιχείο ελέγχου αμέσως και επιστρέφει μια διατηρημένη λαβή IIqControl. Μεταβιβάζετε ένα IIqFormSink που υλοποιεί το πρόσθετό σας· ο host το καλεί (στο κύριο νήμα, με προστασία ορίου) όταν πατηθεί ένα κουμπί, αλλάξει μια τιμή ή κλείσει το παράθυρο.

Η διάρκεια ζωής είναι το λεπτό σημείο:

  • Ο host κρατά το εμφανιζόμενο παράθυρο σε ένα μητρώο, οπότε παραμένει ανοιχτό ακόμη κι αν το πρόσθετό σας απελευθερώσει τη λαβή IIqForm.

  • Όταν το παράθυρο κλείσει — ο χρήστης πατά [X], εσείς καλείτε Close, το πρόσθετό σας απενεργοποιείται ή το IQcad τερματίζεται — ο host καλεί την OnFormClosed μία φορά και στη συνέχεια σημειώνει τη φόρμα και όλες τις λαβές των στοιχείων ελέγχου της ως άκυρες.

  • Μια κλήση σε παρωχημένη λαβή (μετά το κλείσιμο) επιστρέφει με ασφάλεια IQ_E_FAIL — ποτέ αποαναφορά κρεμάμενου δείκτη.

  • Απελευθερώστε τις λαβές σας στην ``Deactivate`` (και μπορείτε να τις αφήσετε στην OnFormClosed). Επειδή οι μονάδες προσθέτων δεν αποφορτώνονται ποτέ, ακόμη και μια λαβή που διέρρευσε είναι ασφαλής — το περιτύλιγμα είναι μνήμη ιδιοκτησίας του host.

7.4.10.1. Ζωντανό παράθυρο με κουμπί που ενημερώνει μια λίστα και μια ετικέτα

Αυτή είναι η καρδιά του δείγματος formdemo. Η εντολή κατασκευάζει μια φόρμα με μια ετικέτα κατάστασης, ένα ακέραιο spin, ένα κουμπί Add και μια λίστα καταγραφής μόνο για ανάγνωση, και στη συνέχεια την εμφανίζει. Η OnButtonClicked του sink διαβάζει την τιμή του spin ζωντανά, προσθέτει μια γραμμή στη λίστα καταγραφής και ενημερώνει την ετικέτα — όλα αυτά ενώ το παράθυρο παραμένει ανοιχτό:

{ Το sink — υλοποιείται από το πρόσθετο· κρατά απλή αναφορά στη φόρμα ώστε
  τα callbacks του να χειρίζονται τα ζωντανά στοιχεία ελέγχου. }
type
  TFormDemoSink = class(TInterfacedObject, IIqFormSink)
  private
    FForm: IIqForm;          // απλή αναφορά — το μητρώο του host κατέχει το παράθυρο
  public
    procedure SetForm(const AForm: IIqForm);
    procedure OnButtonClicked(const Key: PWideChar); cdecl;
    procedure OnValueChanged(const Key: PWideChar); cdecl;
    procedure OnFormClosed; cdecl;
  end;

procedure TFormDemoSink.SetForm(const AForm: IIqForm);
begin FForm := AForm; end;

procedure TFormDemoSink.OnButtonClicked(const Key: PWideChar); cdecl;
var c: IIqControl; n: LongInt;
begin
  if (FForm = nil) or (WideString(Key) <> 'add') then Exit;

  // Ανάγνωση της τιμής του spin ΖΩΝΤΑΝΑ μέσω της λαβής του.
  n := 0;
  if (FForm.FindControl('n', c) = IQ_OK) and (c <> nil) then c.GetInt(n);

  // Προσθήκη γραμμής στη λίστα καταγραφής.
  if (FForm.FindControl('log', c) = IQ_OK) and (c <> nil) then
    c.AppendLine(PWideChar(WideString(WideFormat('Added: %d', [n]))));

  // Ενημέρωση της ετικέτας κατάστασης.
  if (FForm.FindControl('status', c) = IQ_OK) and (c <> nil) then
    c.SetText(PWideChar(WideString(WideFormat('Last added: %d', [n]))));
end;

procedure TFormDemoSink.OnValueChanged(const Key: PWideChar); cdecl;
begin end;

procedure TFormDemoSink.OnFormClosed; cdecl;
begin FForm := nil; end;   // ο host έχει ήδη αποδεσμεύσει το παράθυρο

{ Η εντολή — κατασκευάζει και εμφανίζει τη φόρμα. }
function TFormDemoCommand.Invoke(const Args: IIqEnumerator): TIqResult; cdecl;
var ui: IIqHostUI; frm: IIqForm; c: IIqControl; sink: TFormDemoSink;
begin
  ui := nil; FHost.UI(ui);
  if ui = nil then Exit(IQ_OK);              // headless — τίποτα προς εμφάνιση

  sink := TFormDemoSink.Create;
  FSink := sink;                             // διατήρηση του sink σε ζωή

  frm := nil;
  if (ui.CreateForm('Form demo', FSink, frm) <> IQ_OK) or (frm = nil) then
  begin FSink := nil; Exit(IQ_OK); end;      // δεν υπάρχει πάροχος φορμών
  sink.SetForm(frm);
  FForm := frm;                              // διατήρηση — απελευθέρωση στην Deactivate

  frm.AddLabel ('status', 'Click Add to append a value', c);
  frm.AddInt   ('n', 'Value', 1, 0, 1000, c);
  frm.AddButton('add', 'Add', c);
  frm.AddList  ('log', 'Log', c);

  frm.Show;                                  // μη αποκλειστικό — επιβιώνει της Invoke
  Result := IQ_OK;
end;

Απελευθερώστε τις διατηρημένες λαβές FForm και FSink στην Deactivate (το ήδη εμφανιζόμενο παράθυρο παραμένει ζωντανό στο μητρώο του host σε κάθε περίπτωση). Η πλήρης, μεταγλωττίσιμη έκδοση — συμπεριλαμβανομένου του αντικειμένου του προσθέτου και του manifest με min_minor: 4 — είναι το δείγμα formdemo / cformdemo.

7.4.10.2. Οι ίδιες ζωντανές φόρμες από σενάρια

Οι ζωντανές φόρμες είναι μέρος της κοινής μηχανής περιβάλλοντος χρήστη, οπότε τις ανοίγουν και τα σενάρια Python και Pascal.

Python (δείτε Αναφορά σεναρίων Python) — η iqcad.create_form(title, handler) επιστρέφει μια ζωντανή φόρμα· οι μέθοδοι on_button_clicked(key) / on_value_changed(key) / on_form_closed() του προαιρετικού αντικειμένου handler καλούνται από τον 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 (δείτε Σενάρια Pascal) — η CreateForm(title) επιστρέφει ένα TScriptForm· εκχωρήστε ονόματα διαδικασιών σεναρίου στους χειριστές συμβάντων του:

frm := CreateForm('Form demo');
frm.AddInt('n', 'Value', 1, 0, 1000);
frm.AddButton('add', 'Add');
frm.AddList('log', 'Log');
frm.OnButtonClicked := 'HandleClick';   { όνομα διαδικασίας του σεναρίου }
frm.Show;

7.4.10.3. Πρόσδεση πάνελ

Ένα ζωντανό παράθυρο εργαλείου κανονικά αιωρείται. Η IIqHostUI.CreatePanel (API 1.5) είναι η προσδεμένη εναλλακτική: είναι πανομοιότυπη με την CreateForm — ίδια ορίσματα, ίδιος κατασκευαστής IIqForm επιστρέφεται, ίδιες λαβές IIqControl και ίδια callbacks IIqFormSink — αλλά αντί για αιωρούμενο παράθυρο, τα στοιχεία ελέγχου της φόρμας φιλοξενούνται ως καρτέλα σε μια προσδεμένη λωρίδα στα δεξιά του κύριου παραθύρου, ανάμεσα στον καμβά και τον Επιθεωρητή Αντικειμένων. Η λωρίδα εμφανίζεται όταν προσδεθεί το πρώτο πάνελ και κρύβεται ξανά όταν κλείσει η τελευταία καρτέλα της. Η Show προσθέτει (ή ενεργοποιεί) την καρτέλα· η Close — ή το κλείσιμο της καρτέλας από τον χρήστη — την αφαιρεί και καλεί την OnFormClosed ακριβώς μία φορά. Αιωρούμενο ή προσδεμένο είναι αποκλειστικά επιλογή του προσθέτου: καλέστε CreateForm για αιωρούμενο παράθυρο, CreatePanel για προσδεμένη καρτέλα· τίποτε άλλο στον κώδικά σας δεν αλλάζει.

{ Ακριβώς το παράδειγμα ζωντανής φόρμας, αλλά προσδεμένο. Κατασκευάστε το ίδιο sink και στοιχεία ελέγχου. }
frm := nil;
if (ui.CreatePanel('Form demo', FSink, frm) <> IQ_OK) or (frm = nil) then
begin FSink := nil; Exit(IQ_OK); end;   // δεν υπάρχει πάροχος πρόσδεσης (headless)
sink.SetForm(frm);
FForm := frm;                           // διατήρηση — απελευθέρωση στην Deactivate
{ … AddLabel / AddInt / AddButton / AddList ακριβώς όπως για την CreateForm … }
frm.Show;                               // εμφανίζεται ως καρτέλα στη δεξιά λωρίδα πρόσδεσης

Το ίδιο προσδεμένο πάνελ είναι διαθέσιμο από σενάρια: Python (iqcad.create_panel(title, handler) — δείτε Αναφορά σεναρίων Python) και PascalScript (CreatePanel(title) — δείτε Σενάρια Pascal), καθένα αντίστοιχο της αιωρούμενης create_form / CreateForm.

7.4.11. Συνεισφορές σε κορδέλα και μενού

Οι εντολές που πληκτρολογούνται στη γραμμή εντολών είναι ισχυρές αλλά όχι εύκολα ανακαλύψιμες. Για να εμφανίσει τις εντολές του στο γραφικό περιβάλλον, ένα πρόσθετο προσθέτει κουμπιά κορδέλας και στοιχεία μενού που εκτελούν μια καταχωρισμένη εντολή. Δύο μέθοδοι της IIqHostUI (API 1.6) το κάνουν αυτό:

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)

Κάθε συνεισφορά καταλήγει σε μια αποκλειστική θέση — μία μοναδική καρτέλα κορδέλας Plugins και ένα μοναδικό μενού ανωτάτου επιπέδου Plugins. Κάθε πρόσθετο αποκτά το δικό του τμήμα (στην κορδέλα) και υπομενού (στο μενού), με όνομα από το όρισμα Group / Submenu· δώστε κενή συμβολοσειρά για να χρησιμοποιηθεί το όνομα εμφάνισης του προσθέτου. Δεν υπάρχει δυνατότητα συνεισφοράς σε υπάρχουσες καρτέλες ή μενού του host, οπότε τα πρόσθετα δεν συγκρούονται ποτέ.

  • Δεμένες σε εντολή. Το CommandName είναι μια εντολή που καταχωρίσατε μέσω του μητρώου εντολών· το κλικ στο κουμπί ή στο στοιχείο μενού την εκτελεί μέσω της κανονικής διαδρομής εντολών του host. Δεν υπάρχει ξεχωριστό callback κλικ. Η συνεισφορά δημιουργείται ακόμη κι αν η εντολή δεν έχει καταχωριστεί ακόμη (επιλύεται με το όνομα τη στιγμή του κλικ)· το κλικ σε άγνωστη εντολή είναι μια αβλαβής κενή ενέργεια που καταγράφεται στο αρχείο καταγραφής αποσφαλμάτωσης.

  • Εικονίδια είναι ένα PNG σχετικό με τον φάκελο του προσθέτου (τον ίδιο φάκελο με το plugin.json)· ο host το φορτώνει και το κλιμακώνει στις λίστες εικόνων της κορδέλας. Παραλείψτε το (κενό IconFile) για ένα γενικό εικονίδιο προσθέτου· η AddMenuItem είναι μόνο κειμένου και αγνοεί τα εικονίδια. Το Large επιλέγει μεγάλο ή μικρό κουμπί κορδέλας.

  • Αυτόματος καθαρισμός. Οι συνεισφορές επισημαίνονται με το πρόσθετο που τις πρόσθεσε και αφαιρούνται αυτόματα όταν το πρόσθετο απενεργοποιηθεί, επαναφορτωθεί ή όταν τερματιστεί το IQcad. Η καρτέλα και το μενού Plugins κρύβονται ξανά μόλις δεν απομένουν συνεισφορές.

Οι AddRibbonButton / AddMenuItem καλούνται κανονικά στην Activate, μετά την καταχώριση της εντολής:

function TMyPlugin.Activate(const Host: IIqHost): TIqResult; cdecl;
var reg: IIqCommandRegistry; ui: IIqHostUI;
begin
  FHost := Host;
  // 1. Καταχώριση της εντολής (πρέπει να επιτύχει).
  if Host.Commands(reg) <> IQ_OK then Exit(IQ_E_FAIL);
  reg.Register(TMyCommand.Create(Host));      // όνομα εντολής 'MYCMD'

  // 2. Εμφάνισή της στο γραφικό περιβάλλον (ανεκτικότητα σε headless host).
  ui := nil; Host.UI(ui);
  if ui <> nil then
  begin
    ui.AddRibbonButton('Demo', 'MYCMD', 'Hello', 'Say hello', 'icon.png', True);
    ui.AddMenuItem('Demo', 'MYCMD', 'Hello');
  end;
  Result := IQ_OK;
end;

Ένας headless host (ή μια έκδοση χωρίς πάροχο συνεισφορών) επιστρέφει IQ_E_FAIL από αυτές τις μεθόδους· ανεχτείτε το — η ίδια η εντολή παραμένει καταχωρισμένη και χρησιμοποιήσιμη από τη γραμμή εντολών.

7.4.11.1. Οι ίδιες συνεισφορές από σενάρια

Η μηχανή συνεισφορών είναι κοινή με τους hosts σεναρίων, οπότε και τα σενάρια Python και Pascal προσθέτουν κουμπιά κορδέλας και στοιχεία μενού. Τα σενάρια δεν έχουν φάκελο εγκατάστασης ή id, οπότε οι συνεισφορές τους εμφανίζονται κάτω από μία μοναδική ομάδα / υπομενού Scripts, και το icon είναι απόλυτη διαδρομή.

Python (δείτε Αναφορά σεναρίων Python) — iqcad.add_ribbon_button(group, command, caption, hint='', icon='', large=True) και iqcad.add_menu_item(submenu, command, caption).

PascalScript (δείτε Σενάρια Pascal) — AddRibbonButton(group, command, caption, hint, iconfile, large) και AddMenuItem(submenu, command, caption).

7.4.12. Συγγραφή προσθέτου σε C ή C++

Επειδή το ABI αποτελείται από απλά vtables C, μπορείτε να γράψετε ένα πρόσθετο σε C/C++ (ή σε οποιαδήποτε γλώσσα με C FFI). Συμπεριλάβετε το iqcad_sdk.h και υλοποιήστε τις ίδιες διεπαφές ως δομές vtable.

Εκεί όπου η Pascal σάς δίνει διεπαφές της γλώσσας και την IqStr, στη C κατασκευάζετε το vtable με το χέρι και παρέχετε μόνοι σας τις συμβολοσειρές UTF-16. Ένα ελάχιστο πρόσθετο εντολής:

#include "iqcad_sdk.h"
#include <string.h>

/* ---- ένα επαναχρησιμοποιήσιμο στατικό αντικείμενο συμβολοσειράς UTF-16 -- */
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 }

/* ---- η εντολή ------------------------------------------------------- */
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 };

/* ---- το αντικείμενο του προσθέτου (Describe/Activate/Deactivate) ---- */
/* … ίδιο μοτίβο: μια δομή με const IIqPluginVtbl*, συμπληρώστε τις θέσεις,
   η Activate() καλεί host->Commands(host,&reg); reg->Register(reg, &g_cmd) … */

IQ_EXPORT iq_result IqcadPluginInit(IIqHost* host, IIqPlugin** out_plugin) {
    g_cmd.host = host;
    *out_plugin = /* διεύθυνση του singleton IIqPlugin σας */;
    return IQ_OK;
}

Μεταγλωττίστε το ως κοινόχρηστη βιβλιοθήκη και εγκαταστήστε το ακριβώς όπως ένα πρόσθετο Pascal:

gcc -shared -fPIC -std=c11 -I/path/to/ICAD/cad/plugins/csdk \
    -o libCHello.so chello.c

Δείτε το δείγμα chello για την πλήρη, μεταγλωττίσιμη έκδοση, συμπεριλαμβανομένου του αντικειμένου του προσθέτου και των λεπτομερειών της QueryInterface.

Σημείωση

Στη C καλείτε κάθε μέθοδο ως obj->lpVtbl->Method(obj, args…) — ο δείκτης του αντικειμένου είναι πάντα το έμμεσο πρώτο όρισμα. Οι συμβολοσειρές είναι iq_wchar* (UTF-16)· χρησιμοποιήστε literals u"…". Τα χρώματα χρησιμοποιούν τη μακροεντολή IQ_RGB(r,g,b).

7.4.13. Τα συνοδευτικά δείγματα

Το SDK συνοδεύεται από δεκαοκτώ λυμένα δείγματα στο cad/plugins/samples/ — εννέα σε Free Pascal, το καθένα με δίδυμο σε C. Αποτελούν μια διαδρομή μάθησης από το τετριμμένο έως τα έργα πολιτικού μηχανικού:

#

Pascal

Δίδυμο C

Εντολή (ψευδώνυμο)

Επιδεικνύει

1

hello

chello

HELLO (HI) / CHELLO (CHI)

Τον ελάχιστο σκελετό: σημείο εισόδου, IIqPlugin, μία εντολή, Host.Log. Η έκδοση Pascal εγγράφεται επίσης σε ένα συμβάν.

2

geom

cgeom

DRAWDEMO (DD)

Σχεδίαση γεωμετρίας: CreateLayer + SetActiveLayer, AddLine / AddCircle / AddArc / AddRectangle / AddHatch, ανάγνωση λαβών οντοτήτων, Invalidate. Απαιτεί API ≥ 1.2.

3

entsel

centsel

RECOLOR (RC)

Επιλογή και οντότητες: διάσχιση των Selection / Entities με IIqEnumerator, QueryInterface καθενός σε IIqEntity, SetColor(IqRGB(…)).

4

events

cevents

(καμία — ακροατής)

Συμβάντα: Subscribe ενός IIqEventSink, υλοποίηση και των εννέα callbacks, απελευθέρωση του cookie στην Deactivate. Απαιτεί API ≥ 1.2.

5

civilui

ccivilui

SURVEYDEMO (SD)

Πολιτικός μηχανικός + UI: IIqHostUI.PromptString / MessageBox, AddSurveyPoint, CreateSurface (TIN), ElevationAt.

6

dialogdemo

cdialogdemo

DIALOGDEMO (DLG)

Διάλογος παραμέτρων: CreateDialog + Add* (choice/float/text/ color/bool) + Execute + Get*, στη συνέχεια σχεδιάζει το σχήμα και εμφανίζει εμπλουτισμένο MessageBox. Απαιτεί API ≥ 1.3.

7

formdemo

cformdemo

FORMDEMO (FORM)

Ζωντανό παράθυρο εργαλείου: CreateForm + στοιχεία ελέγχου + ένα callback κουμπιού IIqFormSink που ενημερώνει μια λίστα/ετικέτα ζωντανά. Απαιτεί API ≥ 1.4.

8

dockdemo

cdockdemo

DOCKDEMO (DOCK)

Προσδεμένο πάνελ: όπως το formdemo αλλά μέσω CreatePanel — ζει ως καρτέλα στη δεξιά λωρίδα πρόσδεσης. Απαιτεί API ≥ 1.5.

9

ribbondemo

cribbondemo

RIBBONDEMO (RBN)

Προσθέτει κουμπί κορδέλας + στοιχείο μενού (με εικονίδιο PNG) που εκτελούν μια εντολή: AddRibbonButton / AddMenuItem στην καρτέλα/μενού Plugins. Απαιτεί API ≥ 1.6.

Προτεινόμενη σειρά: hello → geom → entsel → events → civilui → dialogdemo → formdemo → dockdemo → ribbondemo, με το δίδυμο C δίπλα σε καθένα. Τα hello / chello είναι τα μικρότερα πλήρη παραδείγματα· το samples/README.md περιέχει σημειώσεις μεταγλώττισης και εγκατάστασης για κάθε δείγμα.

7.4.14. Αναφορά API

Αυτή η ενότητα παραθέτει τη δημόσια σύμβαση. Οι υπογραφές είναι σε Pascal (uIqSdk.pas)· η κεφαλίδα C iqcad_sdk.h αντιστοιχεί καθεμία σε θέση vtable με ρητό πρώτο όρισμα self.

7.4.14.1. Κωδικοί αποτελέσματος (TIqResult)

Σταθερά

Τιμή

Σημασία

IQ_OK

0

Επιτυχία.

IQ_E_FAIL

-1

Γενική αποτυχία.

IQ_E_NOTIMPL

-2

Η μέθοδος δεν υλοποιείται για αυτό το αντικείμενο.

IQ_E_INVALIDARG

-3

Δόθηκε μη έγκυρο όρισμα.

IQ_E_NOTFOUND

-4

Το ζητούμενο στοιχείο δεν υπάρχει.

IQ_E_OUTOFBOUNDS

-5

Δείκτης εκτός ορίων.

IQ_E_WRONGTHREAD

-6

Κλήθηκε από νήμα διαφορετικό από το κύριο νήμα.

7.4.14.2. Βασικοί τύποι

TIqVersion = record Major, Minor: Word; end;
TIqPoint3D = record X, Y, Z: Double; end;            PIqPoint3D = ^TIqPoint3D;
TIqColor   = LongInt;                                 // πακεταρισμένο 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;
// απαριθμήσεις πλαισίου μηνύματος (API 1.3)
TIqMsgButtons = (imbOK, imbOKCancel, imbYesNo, imbYesNoCancel);
TIqMsgIcon    = (imiNone, imiInfo, imiWarning, imiError, imiQuestion);
TIqMsgResult  = (imrOK, imrCancel, imrYes, imrNo);

Χρώμα — το TIqColor είναι πακεταρισμένη τιμή RGB, 0x00BBGGRR (το κόκκινο στο χαμηλό byte, όπως ένα COLORREF του Win32). Δεν είναι δείκτης παλέτας ACI. Κατασκευάστε το με IqRGB(r, g, b) / IQ_RGB(r, g, b).

Variant — το TIqVariant μεταφέρει μια βαθμωτή τιμή με τύπο για ιδιότητες οντοτήτων. Το Num περιέχει την ακέραια, κινητής υποδιαστολής ή λογική (0/1) τιμή· το Str είναι έγκυρο μόνο όταν Kind = ivkString. Μεταβιβάζεται πάντα με αναφορά const/out, ποτέ με τιμή.

7.4.14.3. Βοηθητικές ρουτίνες χρόνου εκτέλεσης (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;

Αυτές καλύπτουν τις συνήθεις περιπτώσεις (μια συμβολοσειρά, ένα χρώμα, έναν απαριθμητή). Για να εκθέσετε δικά σας δεδομένα μεταβλητού μήκους, παράγετε από το TInterfacedObject και υλοποιήστε απευθείας την IIqString ή την IIqEnumerator.

7.4.14.4. Σημείο εισόδου

// εξάγεται με το όνομα 'IqcadPluginInit'
function IqcadPluginInit(const Host: IIqHost; out Plugin: IIqPlugin): TIqResult; cdecl;

C: iq_result IqcadPluginInit(IIqHost* host, IIqPlugin** out_plugin);

7.4.14.5. IIqPlugin

Το αντικείμενο που επιστρέφει η IqcadPluginInit σας.

function Describe(out Info: TIqPluginInfo): TIqResult; cdecl;  // το id ΠΡΕΠΕΙ να ταιριάζει με το manifest
function Activate(const Host: IIqHost): TIqResult; cdecl;      // καταχωρίστε εδώ εντολές/συμβάντα
function Deactivate: TIqResult; cdecl;                         // καταργήστε εδώ καταχωρίσεις/εγγραφές

7.4.14.6. IIqHost

Η λαβή σας προς το IQcad, που μεταβιβάζεται στις IqcadPluginInit και Activate.

function ApiVersion: TIqVersion; cdecl;
function ActiveDrawing(out D: IIqDrawing): TIqResult; cdecl;       // το D μπορεί να είναι nil (κανένα σχέδιο)
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;

7.4.14.7. IIqHostUI

Προτροπές, πλαίσια μηνυμάτων και διάλογοι παραμέτρων. Όλο το περιβάλλον χρήστη κατασκευάζεται και ανήκει στον host (το πρόσθετο δεν αγγίζει ποτέ το LCL — δείτε Διάλογοι παραμέτρων και πλαίσια μηνυμάτων).

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)

7.4.14.8. IIqDialog

Ένας διάλογος παραμέτρων κατασκευασμένος από τον host (API 1.3), που λαμβάνεται από την IIqHostUI.CreateDialog. Περιγράφετε πεδία με τις μεθόδους Add*, καλείτε την Execute και στη συνέχεια διαβάζετε τις τιμές που εισήχθησαν με τις τυποποιημένες μεθόδους Get*. Ο host κατασκευάζει και κατέχει κάθε στοιχείο ελέγχου· το πρόσθετό σας απλώς περιγράφει τη φόρμα, οπότε δεν υπάρχει κίνδυνος UI μεταξύ διαφορετικών runtimes.

{ κατασκευαστές — το Key αναγνωρίζει το πεδίο για τους getters· το Label_ είναι η προτροπή }
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
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;
{ εκτέλεση ως modal — OK = True αν ο χρήστης αποδέχτηκε }
function Execute(out OK: LongBool): TIqResult; cdecl;
{ τυποποιημένοι getters — έγκυροι μετά την Execute }
function GetText  (const Key: PWideChar; out Value: IIqString): TIqResult; cdecl;  // text/multiline/file/folder, και το κείμενο της επιλεγμένης επιλογής
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;    // επιλεγμένος δείκτης
function GetColor (const Key: PWideChar; out Value: TIqColor): TIqResult; cdecl;

Σημασιολογία: Min = Max σημαίνει αριθμητικό πεδίο χωρίς όρια· το Decimals ορίζει την ακρίβεια εμφάνισης των αριθμών κινητής υποδιαστολής· το Options μιας επιλογής είναι μία συμβολοσειρά διαχωρισμένη με TAB και η GetChoice επιστρέφει τον επιλεγμένο δείκτη (η GetText επιστρέφει το κείμενο της επιλεγμένης επιλογής). Ένας getter ασύμβατου είδους επιστρέφει IQ_E_INVALIDARG, ένα άγνωστο κλειδί IQ_E_NOTFOUND και ένα διπλότυπο κλειδί Add IQ_E_INVALIDARG. Η Execute εκτελείται μία μόνο φορά — μια δεύτερη κλήση επιστρέφει το πρώτο αποτέλεσμα. Όταν δεν υπάρχει διαθέσιμος πάροχος διαλόγων (headless), η Execute επιστρέφει OK = False και η MessageBox επιστρέφει imrCancel.

7.4.14.9. IIqForm

Ένα ζωντανό, μη αποκλειστικό παράθυρο εργαλείου κατασκευασμένο από τον host (API 1.4), που λαμβάνεται από την IIqHostUI.CreateForm. Σε αντίθεση με τον αποκλειστικό IIqDialog, μια φόρμα παραμένει ανοιχτή αφού επιστρέψει η κλήση: προσθέτετε στοιχεία ελέγχου (κάθε Add* κατασκευάζει ένα πραγματικό στοιχείο ελέγχου αμέσως και επιστρέφει μια διατηρημένη λαβή IIqControl), την εμφανίζετε με Show, και στη συνέχεια τροποποιείτε τα στοιχεία ελέγχου κατά την εκτέλεση και αντιδράτε στην είσοδο του χρήστη μέσω ενός IIqFormSink. Ο host κατέχει το παράθυρο και κάθε στοιχείο ελέγχου — δείτε Ζωντανά παράθυρα εργαλείων.

{ κατασκευαστές — καθένας δημιουργεί ένα στοιχείο ελέγχου αμέσως και επιστρέφει τη διατηρημένη λαβή του·
  το Key αναγνωρίζει το στοιχείο ελέγχου για τα συμβάντα και την 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
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;
{ ομαδοποίηση διάταξης (προαιρετική) — τα στοιχεία ελέγχου που προστίθενται μεταξύ Begin/EndRow μοιράζονται μια γραμμή }
function BeginRow: TIqResult; cdecl;
function EndRow: TIqResult; cdecl;
{ αναζήτηση }
function FindControl(const Key: PWideChar; out C: IIqControl): TIqResult; cdecl;
{ κύκλος ζωής }
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;

Τα στοιχεία ελέγχου στοιβάζονται αυτόματα κατακόρυφα (ετικέτα + επεξεργαστής ανά γραμμή) με τη σειρά προσθήκης· οι BeginRow / EndRow ομαδοποιούν πολλά στοιχεία ελέγχου σε μία γραμμή. Ένα διπλότυπο κλειδί Add επιστρέφει IQ_E_INVALIDARG· η FindControl με άγνωστο κλειδί επιστρέφει IQ_E_NOTFOUND. Αφού κλείσει το παράθυρο, η λαβή της φόρμας (και όλες οι λαβές των στοιχείων ελέγχου της) γίνονται αδρανείς — κάθε κλήση επιστρέφει IQ_E_FAIL αντί να αποαναφέρει ένα αποδεσμευμένο στοιχείο ελέγχου.

7.4.14.10. IIqControl

Μια διατηρημένη λαβή προς ένα ζωντανό στοιχείο ελέγχου (API 1.4), που επιστρέφεται από τους κατασκευαστές της IIqForm ή την FindControl. Είναι μία ενιαία γενική λαβή της οποίας οι μέθοδοι πρόσβασης ελέγχουν το είδος: ένας getter ή setter που δεν ταιριάζει στο είδος του στοιχείου ελέγχου επιστρέφει IQ_E_INVALIDARG (π.χ. GetInt σε ετικέτα ή λειτουργία λίστας σε κουμπί).

function Key(out S: IIqString): TIqResult; cdecl;
function GetText  (out Value: IIqString): TIqResult; cdecl;   // text/multiline/file/folder· κείμενο επιλεγμένης επιλογής· λεζάντα ετικέτας/κουμπιού
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;  // κείμενο κουμπιού/ετικέτας
function SetEnabled(Enabled: LongBool): TIqResult; cdecl;
function SetVisible(Visible: LongBool): TIqResult; cdecl;
{ λειτουργίες μόνο για λίστες (άλλα είδη → 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;

Μόλις κλείσει η φόρμα, η λαβή είναι αδρανής και κάθε μέθοδος επιστρέφει IQ_E_FAIL — είναι πάντα ασφαλές να κρατάτε (ακόμη και να καλείτε) μια παρωχημένη λαβή.

7.4.14.11. IIqFormSink

Υλοποιείται από το πρόσθετό σας και παραδίδεται στην CreateForm (μπορεί να είναι nil για μια φόρμα που δεν χρειάζεται χειρισμό συμβάντων). Ο host το καλεί όταν ο χρήστης αλληλεπιδρά με τη φόρμα.

{ IIqFormSink — υλοποιείται από το πρόσθετό σας }
procedure OnButtonClicked(const Key: PWideChar); cdecl;   // πατήθηκε ένα κουμπί με αυτό το Key
procedure OnValueChanged(const Key: PWideChar); cdecl;    // άλλαξε η τιμή ενός επεξεργαστή· διαβάστε τη μέσω του στοιχείου ελέγχου
procedure OnFormClosed; cdecl;                            // το παράθυρο έκλεισε (καλείται μία φορά)

Κάθε callback καλείται στο κύριο νήμα και περιβάλλεται από προστασία ορίου στον host, οπότε μια εξαίρεση στον χειριστή σας δεν μπορεί να διαφύγει στον βρόχο μηνυμάτων του IQcad (πανομοιότυπα με τη γέφυρα IIqEventSink). Η OnValueChanged μεταφέρει το κλειδί του στοιχείου ελέγχου· διαβάστε τη νέα τιμή μέσω της λαβής του στοιχείου ελέγχου (FindControl + ένας τυποποιημένος getter).

7.4.14.12. IIqCommand και IIqCommandRegistry

Εσείς υλοποιείτε την IIqCommand· ο host παρέχει την IIqCommandRegistry.

{ IIqCommand — υλοποιείται από το πρόσθετό σας }
function Name(out S: IIqString): TIqResult; cdecl;
function Aliases(out S: IIqString): TIqResult; cdecl;     // διαχωρισμένα με κόμμα, π.χ. 'RC,REC'
function Category(out S: IIqString): TIqResult; cdecl;
function HelpText(out S: IIqString): TIqResult; cdecl;
function Invoke(const Args: IIqEnumerator): TIqResult; cdecl;   // το Args αποδίδει IIqString

{ IIqCommandRegistry — παρέχεται από την Host.Commands }
function Register(const Cmd: IIqCommand): TIqResult; cdecl;
function Unregister(const Name: PWideChar): TIqResult; cdecl;
function Execute(const Input: PWideChar): TIqResult; cdecl;     // εκτέλεση άλλης γραμμής εντολών

Τα ψευδώνυμα υποστηρίζονται πλήρως — η πληκτρολόγηση ενός ψευδωνύμου εκτελεί την εντολή ακριβώς όπως το κανονικό της όνομα.

7.4.14.13. IIqDrawing

Το ενεργό σχέδιο. Οι μέθοδοι με την ένδειξη (1.2) προστέθηκαν στο 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;       // αποδίδει IIqEntity
function Selection(out E: IIqEnumerator): TIqResult; cdecl;      // αποδίδει IIqEntity
function FindByHandle(const H: PWideChar; out E: IIqEntity): TIqResult; cdecl;
function Layers(out E: IIqEnumerator): TIqResult; cdecl;         // αποδίδει 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;       // αποδίδει 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;     // αποδίδει 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)

7.4.14.14. IIqEntity και 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;

Οι ιδιότητες των σημείων τοπογραφίας προσπελαύνονται μέσω των GetProp / SetProp με τα γνωστά ονόματα PointNumber, PointName, Northing, Easting, Elevation, RawDescription, FullDescription, PointGroup, LockPoint, LabelStyleName (σταθερές IQ_PROP_* στη σύμβαση).

7.4.14.15. Πολιτικός μηχανικός: 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;  // αποδίδει 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;       // αποδίδει IIqAlignmentElement

{ IIqAlignmentElement }
function Kind(out S: IIqString): TIqResult; cdecl;   // 'Line' | 'Arc' | 'Spiral'
function Length: Double; cdecl;
function StartStation: Double; cdecl;
function Radius: Double; cdecl;

7.4.14.16. Συμβάντα: IIqEventSource και IIqEventSink

Υλοποιήστε την IIqEventSink· εγγραφείτε μέσω Host.Events → Subscribe. Η απελευθέρωση του cookie που επιστρέφεται καταργεί την εγγραφή.

{ IIqEventSource — από την Host.Events }
function Subscribe(const Sink: IIqEventSink; out Cookie: IInterface): TIqResult; cdecl;

{ IIqEventSink — υλοποιείται από το πρόσθετό σας (εννέα 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;

Για να σταματήσετε την ακρόαση, ορίστε τη μεταβλητή όπου αποθηκεύσατε το cookie σε nil. Κάθε callback περιβάλλεται από προστασία ορίου στον host, οπότε μια εξαίρεση στον χειριστή σας δεν μπορεί να διαφύγει στο IQcad.

7.4.14.17. Συμβολοσειρές, απαριθμητές, runnables

{ IIqString }
function Length: LongInt; cdecl;
function Chars: PWideChar; cdecl;        // UTF-16 με τερματισμό NUL, έγκυρο για όσο ζει το αντικείμενο

{ IIqEnumerator — καλέστε MoveNext ΠΡΙΝ από Current }
function MoveNext: LongBool; cdecl;
function Current(out Obj: IInterface): TIqResult; cdecl;   // QueryInterface στον συγκεκριμένο τύπο
procedure Reset; cdecl;

{ IIqRunnable — για την PostToMainThread }
procedure Run; cdecl;

Διασχίστε έναν απαριθμητή ως εξής:

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));   // αλλαγή χρώματος κάθε επιλεγμένης οντότητας
end;

7.4.15. Αποσφαλμάτωση προσθέτων

Το IQcad δρομολογεί τη δραστηριότητα προσθέτων και σεναρίων σε ένα αρχείο καταγραφής αποσφαλμάτωσης ώστε να μπορείτε να διαγνώσετε αποτυχίες φόρτωσης και να ανιχνεύσετε τη συμπεριφορά χωρίς debugger.

7.4.15.1. Ενεργοποίηση του αρχείου καταγραφής αποσφαλμάτωσης

Ορίστε "debugLog": true στο settings.json του IQcad. Τότε το IQcad γράφει το iqcad_debug.log δίπλα στο εκτελέσιμο, με εκκένωση (flush) μετά από κάθε γραμμή ώστε οι εγγραφές να επιβιώνουν μιας κατάρρευσης.

7.4.15.2. Τι καταγράφεται

  • Κύκλος ζωής προσθέτων (κατηγορία Plugin) — εντοπισμός, κάθε στάδιο φόρτωσης και ενεργοποίησης, κάθε FAIL/EXCEPTION με την αιτία του, και απενεργοποίηση. Αν ένα πρόσθετο δεν φορτώνεται, αυτό το αρχείο καταγραφής δείχνει ακριβώς πού σταμάτησε.

  • Καταχώριση/κατάργηση καταχώρισης εντολών προσθέτων (κατηγορία Plugin).

  • Οι δικές σας κλήσεις ``Host.Log`` — κάθε Host.Log(level, msg) γράφεται με το επίπεδό του. Το illDebug πηγαίνει μόνο στο αρχείο καταγραφής (χωρίς να γεμίζει τη γραμμή εντολών)· τα illInfo / illWarning / illError πηγαίνουν στο αρχείο καταγραφής και στη γραμμή εντολών. Έτσι έχετε ένα αθόρυβο κανάλι αποσφαλμάτωσης μαζί με μηνύματα ορατά στον χρήστη.

  • Σενάρια (κατηγορία Script) — έξοδος σεναρίων και εκτέλεση εντολών.

7.4.15.3. Εντοπισμός εντολών προσθέτων

  • Κατά τη φόρτωση, κάθε καταχωρισμένη εντολή ανακοινώνεται στη γραμμή εντολών: Plugin command available: HELLO  (aliases: HI).

  • Η HELP τελειώνει με μια ενότητα Loaded plugin commands που παραθέτει κάθε εντολή που προστέθηκε από πρόσθετα, με ψευδώνυμα και κείμενο βοήθειας, ώστε οι χρήστες να βλέπουν πάντα ποιες εξωτερικές εντολές είναι διαθέσιμες.

7.4.16. Μεταγλώττιση για άλλες πλατφόρμες

Ο ίδιος πηγαίος κώδικας μεταγλωττίζεται και στους τρεις στόχους· μόνο το όνομα του αρχείου εξόδου και το κλειδί entry του manifest διαφέρουν:

Κλειδί πλατφόρμας

Εργαλειοθήκη

Όνομα εξόδου

Παράδειγμα

x86_64-linux

lazbuild / fpc, gcc

lib<Name>.so

libMyFirstPlugin.so

x86_64-win64

lazbuild / fpc, MinGW gcc

<Name>.dll

MyFirstPlugin.dll

aarch64-darwin

lazbuild / fpc, clang

lib<Name>.dylib

libMyFirstPlugin.dylib

Μπορείτε να διανείμετε έναν φάκελο προσθέτου που περιέχει και τα τρία δυαδικά αρχεία μαζί με ένα μόνο plugin.json του οποίου ο χάρτης entry ονομάζει το καθένα· το IQcad φορτώνει μόνο εκείνο που ταιριάζει με την πλατφόρμα στην οποία εκτελείται.

7.4.17. Εκδόσεις και συμβατότητα — σύνοψη

  • Η σύμβαση είναι η API 1.6 (IQSDK_API_MAJOR = 1, IQSDK_API_MINOR = 6). Η 1.6 πρόσθεσε τις συνεισφορές σε κορδέλα και μενού (IIqHostUI.AddRibbonButton / AddMenuItem)· η 1.5 πρόσθεσε προσδέσιμα πάνελ εντός του παραθύρου (IIqHostUI.CreatePanel)· η 1.4 πρόσθεσε ζωντανές μη αποκλειστικές φόρμες (IIqForm / IIqControl / IIqFormSink + CreateForm)· η 1.3 πρόσθεσε τον διάλογο παραμέτρων (IIqDialog / CreateDialog) και το εμπλουτισμένο MessageBox· η 1.2 πρόσθεσε τις επιπλέον μεθόδους της IIqDrawing και την πλήρη IIqEventSink εννέα συμβάντων.

  • Δηλώστε το ελάχιστο που χρειάζεστε στο apiVersion — ίδιο major και min_minor όχι μεγαλύτερο από αυτό του host.

  • Οι διατάξεις των διεπαφών που έχουν κυκλοφορήσει είναι παγωμένες· η νέα λειτουργικότητα έρχεται ως μέθοδοι που προσαρτώνται στο τέλος (αύξηση δευτερεύουσας έκδοσης) ή ως νέες διεπαφές.

  • Μέχρι το SDK να δηλωθεί σταθερό, μεταγλωττίζετε ξανά τα πρόσθετα με το τρέχον SDK κάθε φορά που ενημερώνετε το IQcad.