String einlesen und mit subprocess.call Befehl weiter geben

Fragen zu Tkinter.
Antworten
BlueRobot
User
Beiträge: 3
Registriert: Donnerstag 31. März 2022, 13:36

Hallo an die Community!

Ich habe noch nie ein Prg. in Python erstellt, benötigte es jetzt aber mal, wegen ein Roboter Steuerungssoftware, wo man über Python Skripte versiertere Aktionen
ausführen kann bzw. in meinem Fall muss.

Im Prinzip benötigt das Python Skript nur ein String, welches ich "scantime" genannt habe, das über so ein tkinter Eingabefeld, eingegeben werden kann, um
anschließend diese Variable über ein Button "Mit Scantime starten" ein lesen zu können.

Danach soll mit Hilfe dieser String Variable, über ein Button "Start Scan", ein subprocess.call Befehl ausgeführt werden.
Dieser Befehl ruft eine C++ SDK Skript names "ScanningToMesh.exe" auf, mit der Besonderheit, dass direkt mit Komma getrennt, danach der String scantime an die main Funktion
im C++ Skript (sieht in C++ wie folgt aus: int main(int argc, char *argv[ ], wobei scantime in argv[1] übergeben wird) übergeben wird.

Gebe ich die scantime = "15000" direkt am Anfang ein, dann funktioniert der gesamte Scanablauf ohne Probleme.
Versuche ich über die tkinter Eingabemaske diesen String ein zu geben, um es eben bedienerfreundliche zu machen, wird im subprocess.call Befehl die scantime nie richtig
ausgeführt bzw. läuft immer auf error in Python!

Ich wäre hier für ein Code Bespiel, welcher funktioniert, sehr dankbar!
Kann auch gerne mit einem Button für Einlesen der String Variablen und Ausführen des Start Scans sein.
Hinweise: Die String Variable scantime muss an C++ wirklich als String weiter gegeben werden, da sonst dort das Skript nicht funktioniert!

Danke schon mal vorab für die Mühe an die jenigen, die sich Zeit genommen haben!

Hier mein Python Code zum Nachvollziehen:

from robolink import * # RoboDK API
from robodk import * # Robot toolbox
from tkinter import *
import subprocess
import tkinter as tk

window = tkinter.Tk()

#mit direkt vorgegebener scantime = "15000" (15000 entspricht 15s) funktioniert das Programm
#scantime = "15000"

scantime = tk.StringVar()

def Get_Scantime():
scantime.set(scantime.get())
print(scantime.get())

def CloseWindow():
window.destroy()
quit(0)

def PushButton():
def thread_ButtonSelect():
RDK = Robolink()
subprocess.call(['C:\\Program Files (x86)\\SKAPI\\SKAPI\\Exposure\\ScanningToMesh\\x64\\Release\\ScanningToMesh.exe', scantime])

threading.Thread(target=thread_ButtonSelect).start()

window_title = ' ' #in window_title Variable mit String zuweisen
window.title(window_title)

window.protocol("WM_DELETE_WINDOW", CloseWindow)

btnSelect = Button(window, text = 'Start Scan', height = 5, width = 20, command=PushButton)
btnSelect.grid(row=0, column=0)

EmbedWindow(window_title)

scantime_eingabe_label = tk.Label(window, text="Scantime in ms: ", height=3, width=15)
scantime_eingabe_label.grid(row=1, column=0)

scantime_entry = tk.Entry(window, textvariable=scantime, bg='white')
scantime_entry.grid(row=2, column=0)

scan_button = tk.Button(window, text="Mit Scantime starten", command=Get_Scantime)
scan_button.grid(row=2, column=1)

window.mainloop()
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

*-Importe sind schlecht, weil man nicht nachvollziehen kann, woher welcher Name kommt. `tkinter` wird zum Beispiel nirgends als Name importiert, Du benutzt es aber trotzdem.
Funktionsnamen schreibt man wie Variablennamen komplett klein.
Man benutzt keine globalen Variablen, alles was eine Funktion braucht, muß sie über ihre Argumente bekommen. Irgendwo tief verschachtelt benutzt Du dann scantime, einfach so, anstatt wie Du das bei Get_Scantime machst, Dir den Wert geben zu lassen.
`threading` kommt auch aus dem Nichts, muß wohl irgendwo in robodk oder robolink definiert sein. Das kann aber niemand nachvollziehen.
Es fehlt eine main-Funktion, um die ganzen globalen Variablen los zu werden.
BlueRobot
User
Beiträge: 3
Registriert: Donnerstag 31. März 2022, 13:36

Danke an die schnelle erste Antwort von Sirius3!

Die Importe sind über RoboDK im Prinzip so vorgegeben. Man muss hier wissen, dass ich mein Python-Skript innerhalb von RoboDK benutzen muss,
da hiermit letzt endlich am Ende über ein C++ SDK Konsolenanwendung ein Scanner aktiviert wird.

Im Prinzip benötige ich im Python Skript nur über ein Button, das ein subprocess.call Befehl meine .exe aufgerufen wird und ein Eingabefeld für ein String, wo ich meine scantime eingaben und dann übergeben kann.

1. Eingabefeld vom Datentyp String
2. Button für Ausführung des subprocess.call Befehls mit richtig gesetztem String scantime

Mehr muss das Python Skrip nicht können.

Ich versuche mein Anliegen mal anders zu formulieren, vielleicht ist das hier zielführender.

Folgende Variante von meinem Python Skript funktioniert ohne Probleme:

from robolink import * # RoboDK API
from robodk import * # Robot toolbox
from tkinter import *
import subprocess

window = tkinter.Tk()

scantime = "15000" #hier wird die Scandauer von 15s initialisiert

def closeWindow():
window.destroy()
quit(0)

def pushButton():
def thread_ButtonSelect():
RDK = Robolink()
subprocess.call(['C:\\Program Files (x86)\\SKAPI\\SKAPI\\Exposure\\ScanningToMesh\\x64\\Release\\ScanningToMesh.exe', scantime])

threading.Thread(target=thread_ButtonSelect).start()

window_title = '' #in window_title Variable mit String zuweisen
window.title(window_title)

window.protocol("WM_DELETE_WINDOW", closeWindow)

btnSelect = Button(window, text = 'Start Scan', height = 5, width = 20, command=pushButton)
btnSelect.pack(fill = X)

EmbedWindow(window_title)

window.mainloop()
_______________________________________________________________________________________________________________________________________________________________

Im Prinzip muss hier nur noch der Code so ergänzt werden, dass die Stringvariable nicht im Skript selbst vorinitialisiert wird,
sondern über eine tkinter Eingabemaske erfolgt.

Entscheidend ist, dass bei:
subprocess.call(['C:\\Program Files (x86)\\SKAPI\\SKAPI\\Exposure\\ScanningToMesh\\x64\\Release\\ScanningToMesh.exe', scantime])

Am Ende die scantime richtig über die tkinter Maske eingelesen wurde, um danach über das Drücken des Start Buttons die ScanningToMesh.exe mit
dem richtigen Wert von scantime aufgerufen wird!

Über Code Vorschläge, wie man das funktionsfähig bewerkstelligen könnte, wäre ich sehr dankbar!
Ich habe schon einig Varianten probiert, aber bis lang nichts hinbekommen, was richtig funktionert.....

Gruß
Sirius3
User
Beiträge: 17737
Registriert: Sonntag 21. Oktober 2012, 17:20

Naja, mit Raten kommt man ja auch nicht weiter. Ich hatte Dir ja schon die Lösung geschrieben, bzw. weißt Du das ja selbst, sonst hättest Du die Funktion Get_Scantime so nicht schreiben können.
__deets__
User
Beiträge: 14522
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das die importe so vorgegeben sind, ist quatsch. Das SDK aendert nicht die Semantik des Python-Interpreters.

Und ich verstehe dein Problem nicht. Du hast doch schon eine StringVar scantime, deren Inhalt du dir mit scantime.get auch holst. Warum ist das im PushButton-Handler ploetzlich ein Mysterium?
Benutzeravatar
__blackjack__
User
Beiträge: 13068
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

@BlueRobot: Selbst für den Fall dass das SDK vorgeben würde, dass man bestimmte Sachen aus dem SDK-Namensraum benutzen muss, statt gleichnamiges aus der Standardbibliotkek, kann das nicht vorschreiben oder feststellen, dass man dafür diese undurchsichtigen Sternchen-Importe verwenden muss. Gerade bei ”bekannten” Namen wäre es hier um sich wichtiger die explizit zu importieren, um den Leser nicht total zu verwirren.

`quit()` zum Beispiel ist extrem verwirrend. Es gibt ein `quit()` das es eigentlich gar nicht geben dürfte, weil das nur existiert, weil es für den interaktiven Interpreter dokumentiert ist. In normalen Programmen benutzt man das nicht, sondern importiert `sys` um `sys.exit()` zu verwenden. Jetzt ist aber dem Leser gar nicht klar ob hier das `quit()` vom Python-Interpreter gemeint ist, oder ob aus einem der beiden Robo*-Module vielleicht eine `quit()`-Funktion kommt. Oder vielleicht sogar über den `tkinter`-Sternchen-Import. Oder weisst Du bei den ca. 140 Namen die Du damit importierst ob da ein `quit()` dabei ist oder nicht? Ich musste nachschauen.

Letztlich ist die Frage ob man das überhaupt braucht, denn das WM_DELETE_WINDOW hat als Standard schon die Funktion das Fenster zu zerstören, und wenn dass das Hauptfenster ist, dann endet damit auch die Tk-Hauptschleife und das Programm ist ganz normal regulär zuende und liefert dem Aufrufer den Rückgabecode 0. Da muss man nichts extra für machen.

Namen sollten keine kryptischen Abkürzungen enthalten. Wenn man `button` meint, sollte man nicht nur `btn` schreiben. Und wenn man nicht Yoda heisst, sollte die Reihenfolge von Worten ”normal” sein. Eine Button-Auswahl ist nämlich etwas anderes als ein Auswahl-Button, also `select_button` statt `button_select`.

`push_button` wäre ein passender generischer Name für eine Schaltfläche/einen Knopf, aber nicht für eine Funktion. Funktionen werden üblicherweise nach der Tätigkeit benannt, die sie durchführen. Bei Rückruffunktionen manchmal auch nach dem Muster `on_<event name>`, also Beispielsweise `on_select` oder `on_select_button`.

Funktionen verschachtelt man nicht, beziehungsweise nur ganz selten wenn es dafür tatsächlich eine Notwendigkeit gibt. Die ist hier nicht wirklich gegeben. Die innere Funktion sollte auch einen besseren Namen haben. Eine eigene `scan_to_mesh()`-Funktion wäre beispielsweise eine sinnvolle Aufteilung.

`subproces.call()` ist nicht ganz problemfrei, die Dokumentation verweist da auf das neuere `subproces.run()`.

Das könnte dann so aussehen (ungetestet):

Code: Alles auswählen

#!/usr/bin/env python3
import subprocess
import tkinter as tk
from threading import Thread

from robodk import EmbedWindow
from robolink import Robolink

SCAN_TIME = 15_000  # in milliseconds.


def scan_to_mesh(scan_time):
    _ = Robolink()
    subprocess.run(
        [
            (
                R"C:\Program Files (x86)\SKAPI\SKAPI\Exposure\ScanningToMesh"
                R"\x64\Release\ScanningToMesh.exe"
            ),
            str(scan_time),
        ],
        check=True,
    )


def main():
    window_title = ""

    window = tk.Tk()
    window.title(window_title)

    tk.Button(
        window,
        text="Start Scan",
        height=5,
        width=20,
        command=lambda: Thread(
            target=scan_to_mesh, args=[SCAN_TIME], daemon=True
        ).start(),
    ).pack(fill=tk.X)

    EmbedWindow(window_title)

    window.mainloop()


if __name__ == "__main__":
    main()
„All religions are the same: religion is basically guilt, with different holidays.” — Cathy Ladman
BlueRobot
User
Beiträge: 3
Registriert: Donnerstag 31. März 2022, 13:36

Hallo __blackjack__ ,

als erstes Danke für die ausführliche Korrekturen, deren Erläuterung und dem Code Bsp.!
Meine Intention war nicht den Leser zu verwirren, sondern viel mehr, habe ich bestimmte *Imports und andere Befehle so verwendet,
wie ich mir aus Code Bsp. zusammen setzen konnte, welches natürlich auf reiner Unkenntnis meinerseits beruhte!
Daraus folgt, ich kann immer nur den Code verwenden, den ich bisher selbst zusammen setzen konnte und der war in meinem Fall nun mal
verwirrend und für geübte Python Programmierer schlecht nachvollziehbar!

Nichtsdestotrotz, so konnte ich schon einiges über Python lernen, ich selbst bin ja SPS Programmierer und habe darüber hinaus Basiskenntnisse in Hochsprachen wie C# und C++ bzw. etwas VB.
Python ist für mich komplett neu und das hier war mein aller erstes, richtiges Python-Programm.

By the way ich konnte heute auch mit meinem Code letzt endlich das erledigen was ich wollte!
Hab einfach meine get_Scantime() Funktion komplett weg gelassen und statt dessen einfach beim subprocess.call () Befehl bei scantime, einfach scantime.get() verwendet und mit der tk.entry Funktion die scantime eingelesen. Damit funktioniert alles so wie ich es wollte!

Dein Code __blackjack__ habe ich ebenfalls ausprobiert und bis auf, dass ich "from robodk import EmbedWindow" auskommentieren musste, weil er das irgendwie nicht fressen wollte,
hat es ohne Probleme funktioniert! Ich werde jetzt dann noch im Anschluss auch noch versuchen, deinen Code so an zu passen, dass ich auch dort noch die SCAN_TIME über eine get() Funktion bekomme bzw. über eine tk.entry mir einlese und dann sollte es auch für mich gut sein.

Vielen Dank an alle die mir hier geantwortet haben!

Grüße
Antworten