icon

Das Wissen aller Anwender nutzen

Im Allplan Connect Forum tauschen sich Anwender aus, geben wertvolle Tipps oder beraten sich bei ganz konkreten Aufgabenstellungen − auch international.
Und damit wirklich keine Frage unbeantwortet bleibt, unterstützen die Mitarbeiter des Technischen Supports ebenfalls aktiv das Forum.

Es erwarten Sie:

  • Foren-Vielfalt aus CAD Architektur, CAD Ingenieurbau uvm.
  • Tipps von User für User
  • international: Deutsch, Englisch, Italienisch, Französisch und Tschechisch

Melden Sie sich jetzt an und diskutieren Sie mit!

Zur Registrierung

[Frage] Preview von Objekten x Text im BaseScriptObject on_preview_element [Gelöst]

Schlagworte:
  • PythonParts
  • Preview
  • On_preview_draw
  • BaseScriptObject

Hallo zusammen,

ich habe mal nach Beispielen gesucht, in dem im BaseScriptObject eine Preview benutzt wird. Leider dazu nichts gefunden. Ich habe mal mein PythonPart Script hier angehängt. Die Funktion: Selektiere Elemente per Multi Select, ziehe die die Werte von Attribut 507 raus, zähle diese, setzte mit einem weiteren Mausklick den Text ab.

Leider muss der User den Text "blind" an eine Stelle setzten, ich versuche den Text zwischen der Zählung und dem weiteren Mausklick per Preview an das Fadenkreuz zu hängen, doch scheitere daran.
Ich habe zwar hier die Info der Funktion: https://pythonparts.allplan.com/2025/api_reference/GeneralScripts/BaseInteractor/?h=on_pre#BaseInteractor.BaseInteractor.on_preview_draw

Aber keinerlei Beispiele oder was ich damit genau machen muss. Ich habe in meinem Script einfach einen 2D Text per Preview anzeigenwollen, aber das funktioniert nicht.

Ich hoffe, jemand kann mir helfen, und sagen, wie genau das on_preview_draw Funktion aufgebaut und implementiert werden muss.

Gruss Sebastian

Anhänge (1)

Typ: text/plain
391-mal heruntergeladen
Größe: 7,76 KiB

Lösung anzeigen Lösung verbergen

Hi Sebastian,

Du bist dabei, einen ScriptObject zu implementieren. Du solltest also von der BaseScriptObject vererben, nicht vom BaseInteractor (machst du ja auch in deinem beigefügten Script). Und wenn du dir die BaseScriptObject-Klasse anschaust - da gibt es keine on_preview_draw-Methode. In anderen worten: ALLPLAN erwartet von dir nicht, dass du diese methode implementierst und wird diese auch nicht aufrufen.

Wenn dein Workflow aber so ist, wie du es beschrieben hast, brauchst du eigentlich nur MultiElementSelectInteractor um die Elemente zu selektieren. Um den TextElement einfach in einem Punkt abzusetzen, brauchst du den PointInputInteractor nicht. Stattdessen, leere einfach das self.script_object_interactor nach der Selektion.

Warum? Schau mal auf diesen Flow diagramm. Wenn self.script_object_interactor leer ist, ruft ALLPLAN deine execute-Methode auf. Der Output dieser Methode ist eine instanz von CreateElementResult. Dieses Objekt beschreibt, was und wie erstellt werden soll. Wenn es so definiert is:

return CreateElementResult(model_ele_list, placement_point=insert_point_3d)

wo model_ele_list nur dein TextElement im lokalen Koordinatensystem (also Referenzpunkt 0,0,0) beinhaltet, kümmert sich ALLPLAN darum, diesen am Fadenkreuz zu hängen, Vorschau zu generieren, usw. Als ob du ein Standard-PythonPart erstellt hättest.

Also mach folgendes:

Das Ende der execute() Methode soll so aussehen:

        text = AllplanBasisEle.TextElement(
            self.common_props_standard,
            text_prop_besch,
            text_str,
            AllplanGeo.Point2D(),
        )
        model_ele_list.append(text)

        return CreateElementResult(model_ele_list)

Kein Absetzpunkt in CreateElementResult. model_ele_list hat nur TextElement und diese ist am 0,0,0 definiert.

Die start_next_input soll so aussehen:

    def start_next_input(self):
        """Checks selected element and continues the interaction"""
        print("Starte Next Input")

        # Phase 1: Selektion abgeschlossen
        self.text_list = []

        for ele in self.interaction_result.sel_elements:
            attr_dict = dict(ele.GetAttributes(BaseElements.eAttibuteReadState.ReadAll))

            if 507 not in attr_dict:
                continue

            self.text_list.append(str(attr_dict[507]))

        self.script_object_interactor = None

Diese wird nach der erfolgreichen selektion der Elemente aufgerufen. Also attribut 507 durchgehen und selektion beenden bzw. zu einem StandardPythonPart-modus wechseln indem self.script_object_interactor wieder auf None gesetzt wird.

Das wär's. Um Vorschau kümmert sich ALLPLAN.

Angepasstes Skript habe ich beigefügt. Dort erstelle ich aber einfach einen "Test"-Text. Bitte mit deiner Business-Logik ersetzen.

Grüße,
Bart

Anhänge (1)

Typ: text/plain
236-mal heruntergeladen
Größe: 6,38 KiB

Hi Sebastian,

Du bist dabei, einen ScriptObject zu implementieren. Du solltest also von der BaseScriptObject vererben, nicht vom BaseInteractor (machst du ja auch in deinem beigefügten Script). Und wenn du dir die BaseScriptObject-Klasse anschaust - da gibt es keine on_preview_draw-Methode. In anderen worten: ALLPLAN erwartet von dir nicht, dass du diese methode implementierst und wird diese auch nicht aufrufen.

Wenn dein Workflow aber so ist, wie du es beschrieben hast, brauchst du eigentlich nur MultiElementSelectInteractor um die Elemente zu selektieren. Um den TextElement einfach in einem Punkt abzusetzen, brauchst du den PointInputInteractor nicht. Stattdessen, leere einfach das self.script_object_interactor nach der Selektion.

Warum? Schau mal auf diesen Flow diagramm. Wenn self.script_object_interactor leer ist, ruft ALLPLAN deine execute-Methode auf. Der Output dieser Methode ist eine instanz von CreateElementResult. Dieses Objekt beschreibt, was und wie erstellt werden soll. Wenn es so definiert is:

return CreateElementResult(model_ele_list, placement_point=insert_point_3d)

wo model_ele_list nur dein TextElement im lokalen Koordinatensystem (also Referenzpunkt 0,0,0) beinhaltet, kümmert sich ALLPLAN darum, diesen am Fadenkreuz zu hängen, Vorschau zu generieren, usw. Als ob du ein Standard-PythonPart erstellt hättest.

Also mach folgendes:

Das Ende der execute() Methode soll so aussehen:

        text = AllplanBasisEle.TextElement(
            self.common_props_standard,
            text_prop_besch,
            text_str,
            AllplanGeo.Point2D(),
        )
        model_ele_list.append(text)

        return CreateElementResult(model_ele_list)

Kein Absetzpunkt in CreateElementResult. model_ele_list hat nur TextElement und diese ist am 0,0,0 definiert.

Die start_next_input soll so aussehen:

    def start_next_input(self):
        """Checks selected element and continues the interaction"""
        print("Starte Next Input")

        # Phase 1: Selektion abgeschlossen
        self.text_list = []

        for ele in self.interaction_result.sel_elements:
            attr_dict = dict(ele.GetAttributes(BaseElements.eAttibuteReadState.ReadAll))

            if 507 not in attr_dict:
                continue

            self.text_list.append(str(attr_dict[507]))

        self.script_object_interactor = None

Diese wird nach der erfolgreichen selektion der Elemente aufgerufen. Also attribut 507 durchgehen und selektion beenden bzw. zu einem StandardPythonPart-modus wechseln indem self.script_object_interactor wieder auf None gesetzt wird.

Das wär's. Um Vorschau kümmert sich ALLPLAN.

Angepasstes Skript habe ich beigefügt. Dort erstelle ich aber einfach einen "Test"-Text. Bitte mit deiner Business-Logik ersetzen.

Grüße,
Bart

Anhänge (1)

Typ: text/plain
236-mal heruntergeladen
Größe: 6,38 KiB

Danke Bart, das ist hat mir geholfen und ich konnte mit den paar Anpassungen mein Script jetzt voll funktionsfähig machen. Ich habe das Script Flow einfach falsch verstanden. Ich habe es so verstanden:
Ich brauche in meinem BaseScriptObject grundsätzlich keine wie Funktionen wie z.B. on_preview_draw(), dann wird allerdings nichts angezeigt aber kann diesen mit den den aufgelisteten Funktionen ausstatten. Das diese aber gar nicht erst aufgerufen werden, war mir nicht klar. Das der BaseScriptInteractor sich automatisch um eine Preview und einem Absetzpunkt kümmert, war mir nicht klar. Konnte das auch nirgends auf der Doku-Seite vom Script-Object nachlesen oder finden. Vielleicht wäre es gut, diese Info noch dort zu ergänzen.

Nun gut, jetzt weiss ich es

Gruss Sebastian

Hi Sebastian,

was ich jetzt sage, ist ein Programmierwissen - die Programmierer auf diesem Forum werden es für offensichtlich halten. Ich weiß aber, dass viele hier von der Baubranche kommen.

BaseScriptObject ist eine sog. Abstract Base Class (sieht man oben: Bases: ABC und ABC steht für Abstract Base Class). D.h., sie ist nich dafür da, um direkt genutzt zu werden. Stattessen, beschreibt sie einen Vertrag, in Software Development auch Contract bzw. Interface genannt. Diesen Interface muss man erfüllen bzw. implementieren, damit alles funtioniert (in dem Fall, deine PythonPart).

Das Interface beinhaltet u.a. Methoden, die man implementieren muss und die man implementieren kann.

Die Methoden, die man implementieren muss sind die sog. abstrakte Methoden. execute() ist ein Beispiel:

    @abc.abstractmethod
    def execute(self) -> CreateElementResult:
        """  execute the script

        Returns:
            created result
        """

Dein ScriptObject muss diese Methode überschreiben, sonst funktioniert es nicht.

Die Methoden, die man implementieren kann, sind sog. dummy-Methoden. Sie sind in der Base-Klasse zwar definiert, machen aber nichts. Beispiel:

    def modify_element_property(self,
                                _name : str,
                                _value: Any) -> bool:
        """ Modify property of element

        Args:
            _name:  the name of the property.
            _value: new value for property.

        Returns:
            palette update state
        """

        return False

Wenn dein ScriptObject diese Methode nicht überschreibt, fehlt vielleicht ein Feature, aber PythonPart an sich funktioniert. Du hast gedacht, on_preview_draw ist eben so eine Methode in ScriptObject. Ist sie aber nicht. Sie ist aber so eine im Interactor Interface (da ist sie sogar eine Pflicht, sprich als abstrakt definiert).

Du kannst natürlich andere Methoden dazu definieren und diese auch beliebig benennen. Aber ALLPLAN wird diese nie aufrufen, denn diese werden nicht erwartet. Anders ausgedrückt: sind nicht Teil des Interfaces. Wenn dein Code sie nicht woanders verwendet, sind sie überflüssig.

Ich hoffe, das bring etwas mehr Licht in der Sache. Auch für andere, die dieses Forum durchscrollen.

Grüße,
Bart

Hallo Bart,

ein bisschen verstehe ich schon worum es geht.
[i]Während der Programmierung des BaseScriptObject ist mir allerdings noch etwas aufgefallen was ich nicht ganz verstehe, unabhängig mal von diesem Post und meinem Problem, ich kann dieses auch gerne in einem weiteren Post verfassen, aber es betrifft eben das BaseScriptObject und das Flowdiagramm:-
[color=red]
__
Nach dem Flowdiagramm sollte das BaseScriptObject ja eigentlich, sobald die execute durch ist, zu einem "end" kommen. Das selbe gilt über den Cancel_Input.
Wenn ich Escape drücke, dann komme ich auch aus dem PythonPart, aber wenn das ganze Script durchläuft und ich die execute durchlaufen lasse, dann bin ich immer noch in der Funktion. In diesem Fall, sobald der Text final abgesetzt wurde, fragt mich Allplan nach dem Griff siehe Screenshot. Ich kann in diesem Zustand auch keine Teilbildstruktur öffnen oder andere Befehle aktivieren, also ist der User noch im Pythonpart drin

Ich vermute, es wird irgendwie mit der on_cancel_function funktionieren, aber das Diagramm würde diese nur vor Create_Element zum tragen kommen. Wäre es in diesem Zustand nochmal möglich, das ganze BaseScriptObject neu starten? Also eine Art Loop? Nach Flowdiagramm ist das zumindest nicht vorgesehen, aber vielleicht kann ich ja mit einem kleinen Trick start_input, start_next_input und die execute erneut zu starten. Es wird vielleicht irgendwie mit der on_cancel_function() gehen, aber nach Flow Chart ist diese nur bis zum Create Element nutzbar. Danach eben nicht mehr.

Sorry, ich bearbeite den Post, nachdem ich mir das Flow Diagramm erneut angeschaut habe...
Da steht ja ganz klar, dass man nach der Execute Escape drücken kann, und das genau dafür da ist. Nun würde ich aber gern ein RESTART und CREATE_ELEMENT aufrufen.
Allplan soll erst den Text erzeugen, dann den Restart für eine neue Element Selektion. Ist das möglich? So wie ich verstehe eher nicht.

Anhänge (1)

Typ: image/png
6-mal heruntergeladen
Größe: 4,41 KiB

Zitiert von: thesocialpotwal
... Nun würde ich aber gern ein RESTART und CREATE_ELEMENT aufrufen.
Allplan soll erst den Text erzeugen, dann den Restart für eine neue Element Selektion. Ist das möglich? So wie ich verstehe eher nicht.

Hi,

Um das zu erreichen, setze den "multi_placement" Attribut in deinem CreateElementResult objekt auf True.

def execute(self) -> CreateElementResult:
    ...
    return CreateElementResult(model_ele_list, multi_placement=True)

Das alleine reicht. Du brauchst nicht mal die on_cancel_function zu implementieren.

Grüße,
Bart

Perfekt danke, jetzt ist es funktioniert es, wie es soll
Und wieder was gelernt, dass es sowas wie Multi Placement gibt.