DavidJr. 1 Posted June 3 Hi, I am trying to call a Python (with Open3D) app UI from a Delphi form (11.3), using a Tthread .. it seems to call the method just fine but I do not see the window. When not using it in a thread the window comes up just fine... however I want to be able to terminate the process at will (from within the Delphi Form). What do I need to do in order to make the python app be visible? (Note: I am also using SDL Components for the input fields that handle numbers.) unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.Threading, System.Math, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, PythonEngine, PythonVersions, ShellAPI, SDL_NumIO, SDL_stringl, Vcl.ExtCtrls, LJUDDelphi, Vcl.WinXCtrls, TlHelp32; const PYHARV_HOME = 'C:\Users\printeruser\Desktop\3DScan_Work\photoneo-python-examples\GigEV\harvesters'; VENV_PATH = PYHARV_HOME + '\.venv'; VENV_PYTHON_EXE = VENV_PATH + '\Scripts\python.exe'; PHOTONEO_GENICAM_PATH = PYHARV_HOME + '\advanced\photoneo_genicam'; LJ_TIMER_INTERVAL = 100; SCAN_MODE_MANUAL = 0; SCAN_MODE_TIMER = 1; SCAN_MODE_RELAY = 2; // THREAD EVENT_ID_NUMS: CloseScanResultEventID = 0; RunScanGetResultEventID = 1; type TPythonScriptThread = class(TThread) private FPythonEngine: TPythonEngine; FPythonInputOutput: TPythonInputOutput; FPyVersion: TPythonVersion; FScript: TStringList; FDevSerialNumber: String; FHorizontalRotation: Double; FVerticalRotation: Double; FScanZoomFactor: Double; FScriptRunning: Boolean; FPythonOpen3DPID: DWORD; FCloseScanResultEvent: THandle; FRunScanGetResultEvent: THandle; procedure SetEnvironmentVariables; procedure ConfigurePythonEnvironment; procedure Set_DevSerialNumber(Value: String); procedure Set_HorizontalRotation(Value: Double); procedure Set_VerticalRotation(Value: Double); procedure Set_ScanZoomFactor(Value: Double); // Thread Event triggered methods: function _ExecuteStopPythonScript: Boolean; function _ExecuteRunPythonScript: Boolean; // Private Thread member methods that are called by UI invoking a thread-safe event method: function mFindPythonOpen3DSubprocess: DWORD; protected procedure Execute; override; public constructor Create; destructor Destroy; override; // Read Only Property: property ScriptRunning: Boolean read FScriptRunning; // The publicly accessible methods: function StopPythonScript: Boolean; function RunPythonScript(DevID: String; HRot: Double = 0; VRot: Double = 0; Zoom: Double = 1): Boolean; end; TForm1 = class(TForm) Panel1: TPanel; nioVerticalRotation: TNumIO2; PythonEngine1: TPythonEngine; RadioGroup1: TRadioGroup; editDevSerialNumber: TEdit; nioHorizontalRotation: TNumIO2; nioZoomFactor: TNumIO2; btnScanNow: TButton; lblDeviceSerialNum_Label: TLabel; Timer1: TTimer; nioTimerInterval: TNumIO2; lblLabJackStatus: TLabel; uiRelay_0_Voltage: TLabel; uiRelay_1_Voltage: TLabel; procedure btnScanNowClick(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); procedure RadioGroup1Click(Sender: TObject); procedure nioTimerIntervalChange(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure nioZoomFactorChange(Sender: TObject); procedure Panel1DblClick(Sender: TObject); private { Private declarations } PythonScriptThread: TPythonScriptThread; // LabJack Vars for the class: FLabJackConnected: Boolean; FRelayMonitorLatch: Boolean; FLJRelay_0_ON, FLJRelay_1_ON: Boolean; FLJErrorcode: LJ_ERROR; // LabJack Error Code FLJHandle: LJ_HANDLE; // Handle for LabJack device FLJVoltage_0, FLJVoltage_1 : double; // Timer & TDateTime Trigger: LastTriggerTime: TDateTime; // The rest of th eclass vars: FTimerTriggeredScan: Boolean; FRelayTriggeredScan: Boolean; FTimeTrigScanInterval: Integer; procedure RequestElevationIfNeeded; function IsUserAnAdmin: Boolean; // Trigger procedures: procedure TriggerByUserToScan; procedure TriggerByTimerToScan; procedure TriggerByRelayToScan; // LabJack: procedure Initialize_U3HV; procedure ErrorHandler(LJErrorcode: LJ_ERROR; LJIteration: longint); procedure SetDACVoltage(Channel: Integer; TheSetVoltage: Double); procedure GetAnalogVoltage(Channel: Integer; var TheVoltage: Double); public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} // The Rest of the Code: function TForm1.IsUserAnAdmin: Boolean; var hToken: THandle; Elevation: TOKEN_ELEVATION; cbSize: DWORD; begin Result := False; if OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, hToken) then try cbSize := SizeOf(TOKEN_ELEVATION); if GetTokenInformation(hToken, TokenElevation, @Elevation, cbSize, cbSize) then Result := Elevation.TokenIsElevated <> 0; finally CloseHandle(hToken); end; end; procedure TForm1.RequestElevationIfNeeded; var sei: TShellExecuteInfo; exeName: string; begin if not IsUserAnAdmin then begin ZeroMemory(@sei, SizeOf(sei)); sei.cbSize := SizeOf(sei); sei.Wnd := Handle; // Parent window sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI; sei.lpVerb := 'runas'; // Request elevation exeName := ParamStr(0); sei.lpFile := PChar(exeName); sei.lpParameters := PChar(''); // No parameters sei.nShow := SW_SHOWNORMAL; if not ShellExecuteEx(@sei) then begin ShowMessage('Failed to create elevated process. Error code: ' + IntToStr(GetLastError)); Application.Terminate; // Terminate the unelevated instance end else Application.Terminate; // Terminate the unelevated instance end; end; // LabJack: procedure TForm1.Initialize_U3HV; var numChannels, quickSample, longSettling: longint; LabJack_Check : integer; begin // This performs the standard initialization of the U3-HV system // Some initial values LabJack_Check := 0; FLJHandle := 0; numChannels := 16; //Number of AIN channels, 0-16. quickSample := 0; //Set to TRUE for quick AIN sampling. longSettling := 1; //Set to TRUE for extra AIN settling time. // Now let's open the labjack FLJErrorcode := OpenLabJack(LJ_dtU3, LJ_ctUSB, '1', 1, FLJHandle); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); // Now let's reset the configuration FLJErrorcode := ePut(FLJHandle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); //Configure quickSample. FLJErrorcode := ePut(FLJHandle, LJ_ioPUT_CONFIG, LJ_chAIN_RESOLUTION, quickSample, 0); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); //Configure longSettling. FLJErrorcode := ePut(FLJHandle, LJ_ioPUT_CONFIG, LJ_chAIN_SETTLING_TIME, longSettling, 0); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); //Configure the lines as analog. FLJErrorcode := ePut(FLJHandle, LJ_ioPUT_ANALOG_ENABLE_PORT, 0, (IntPower(2, numChannels) - 1), numChannels); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); if LabJack_Check = 0 then begin lblLabJackStatus.Font.Color := clLime; lblLabJackStatus.Caption := 'LabJack Connected'; end else begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; end; // Let's set the DAC0 and DAC1 to 0.0 Volts at boot-up SetDACVoltage(0, 0.0); SetDACVoltage(1, 0.0); end; procedure TForm1.ErrorHandler(LJErrorcode: LJ_ERROR; LJIteration: longint); var err: array[0..254] of AnsiChar; begin if LJErrorcode <> LJE_NOERROR then begin ErrorToString(LJErrorcode, @err); //StatusUpdate('Error number = ' + IntToStr(lngErrorcode)+ ' Error string = ' + string(err) + ' Iteration = ' + IntToStr(LJIteration)); FLabJackConnected := False; // there was an error! end else begin // LabJack Operation is OK FLabJackConnected := True; end; end; procedure TForm1.SetDACVoltage(Channel: Integer; TheSetVoltage: Double); begin if FLabJackConnected then begin // This uses the global lngHandle to refer to the U3-HV if ABS(TheSetVoltage) < 10 then ePUT(FLJHandle, LJ_ioPUT_DAC, Channel, TheSetVoltage, 0) else begin // Add an error statement if the DAC is set overrange...! lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Connected & Overrange!'; end; end else begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; FLabJackConnected := False; ShowMessage('Problem connecting to LabJack! Check Power and USB connection.'); end; end; procedure TForm1.GetAnalogVoltage(Channel: Integer; var TheVoltage: Double); begin // This uses the global lngHandle to read a voltage from the Analog Channels // // The "LJ_ioGET_AIN" is the integer that determines the operation. Refer to // LJUDDelphi.pas for details if FLabJackConnected then begin eGET(FLJHandle, LJ_ioGET_AIN, Channel, TheVoltage, 0); end else begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; FLabJackConnected := False; ShowMessage('Problem connecting to LabJack! Check Power and USB connection.'); end; // Update the Text readings if Channel = 0 then uiRelay_0_Voltage.Caption := 'Relay 0 Voltage = '+ Strff(FLJVoltage_0, 4, 3)+' V'; if Channel = 1 then uiRelay_1_Voltage.Caption := 'Relay 1 Voltage = '+ Strff(FLJVoltage_1, 4, 3)+' V'; end; // Python script thread implementation constructor TPythonScriptThread.Create; begin inherited Create(False); FPythonEngine := TPythonEngine.Create(Form1); FPythonInputOutput := TPythonInputOutput.Create(Form1); if PythonVersionFromPath(PYHARV_HOME + '\.venv\', FPyVersion) then begin // Create the Events: FCloseScanResultEvent := CreateEvent(nil, True, False, nil); FRunScanGetResultEvent := CreateEvent(nil, True, False, nil); FPythonEngine.VenvPythonExe := VENV_PYTHON_EXE; FPythonEngine.SetPythonHome(PYHARV_HOME + '\.venv\'); FPyVersion.AssignTo(FPythonEngine); FPythonEngine.LoadDll; // ShowMessage('Python DLL loaded successfully. ' + PythonEngine1.DllName); ConfigurePythonEnvironment; end end; destructor TPythonScriptThread.Destroy; begin FScript.Free; inherited; end; // The publicly accessible methods: function TPythonScriptThread.StopPythonScript: Boolean; begin //CloseScanResultEventID SetEvent(FCloseScanResultEvent); end; function TPythonScriptThread.RunPythonScript(DevID: String; HRot: Double = 0; VRot: Double = 0; Zoom: Double = 1): Boolean; begin Result := False; // Set the Params: FDevSerialNumber := DevID; FHorizontalRotation := HRot; FVerticalRotation := VRot; FScanZoomFactor:= (100 / Zoom); //RunScanGetResultEventID if(FDevSerialNumber <> '') AND (Not FHorizontalRotation.IsNan) AND (Not FVerticalRotation.IsNan) AND (Not FScanZoomFactor.IsNan) then begin SetEvent(FRunScanGetResultEvent); Result := True; end; end; procedure TPythonScriptThread.Execute; var EventHandles: array[0..1] of THandle; EventIndex: DWORD; begin EventHandles[CloseScanResultEventID] := FCloseScanResultEvent; EventHandles[RunScanGetResultEventID] := FRunScanGetResultEvent; while(Not Terminated) do begin EventIndex := WaitForMultipleObjects(2, @EventHandles, False, 60); case EventIndex of WAIT_OBJECT_0 + CloseScanResultEventID: // FJogStopEvent signaled begin // Handle FCloseScanResultEvent signaled case _ExecuteStopPythonScript; ResetEvent(FCloseScanResultEvent); end; WAIT_OBJECT_0 + RunScanGetResultEventID: // FJogStopEvent signaled begin // Handle FRunScanGetResultEvent signaled case _ExecuteRunPythonScript; ResetEvent(FRunScanGetResultEvent); end; end; end; CloseHandle(FCloseScanResultEvent); CloseHandle(FRunScanGetResultEvent); end; // Python uses Open3D Library modules: function TPythonScriptThread.mFindPythonOpen3DSubprocess: DWORD; var Snapshot: THandle; ProcessEntry: TProcessEntry32; begin Result := 0; Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if Snapshot <> INVALID_HANDLE_VALUE then try ProcessEntry.dwSize := SizeOf(ProcessEntry); if Process32First(Snapshot, ProcessEntry) then begin repeat if Pos('Open3D', ProcessEntry.szExeFile) > 0 then begin Result := ProcessEntry.th32ProcessID; Break; end; until Not Process32Next(Snapshot, ProcessEntry); end; finally CloseHandle(Snapshot); end; end; function TPythonScriptThread._ExecuteStopPythonScript: Boolean; begin end; // The Actual Python execution: function TPythonScriptThread._ExecuteRunPythonScript: Boolean; var Script: TStringList; ScriptFilePath, HorizontalRotation, VerticalRotation, ZoomFactor: string; begin // Prevent Double Clicking... since the python script needs to complete first before re-running: FScriptRunning := True; Script := TStringList.Create; try // Retrieve the values from the form inputs ScriptFilePath := PYHARV_HOME + '\advanced\GridLoigic_pointcloud_with_normals_and_texture.py'; if FileExists(ScriptFilePath) then begin Script.LoadFromFile(ScriptFilePath); HorizontalRotation := FHorizontalRotation.ToString; VerticalRotation := FVerticalRotation.ToString; ZoomFactor := FScanZoomFactor.ToString; // Insert the sys.path and debug statements Script.Insert(2, 'sys.path.append(r"' + PYHARV_HOME + '\advanced")'); Script.Insert(3, 'print("Starting script execution")'); Script.Insert(4, 'try:'); Script.Insert(5, ' import photoneo_genicam'); Script.Insert(6, ' print("photoneo_genicam imported successfully")'); Script.Insert(7, 'except ImportError as e:'); Script.Insert(8, ' print("Failed to import photoneo_genicam:", str(e))'); Script.Insert(9, ' raise'); // Add the parameters as comments for reference Script.Insert(10, '# Parameters passed from Delphi'); Script.Insert(11, 'device_id = "' + FDevSerialNumber + '"'); Script.Insert(12, 'horizontal_angle = ' + HorizontalRotation); Script.Insert(13, 'vertical_angle = ' + VerticalRotation); Script.Insert(14, 'zoom_factor = ' + ZoomFactor); // Replace main function call with parameters //Script.Add('main(device_id, horizontal_angle, vertical_angle, zoom_factor)'); try FPythonEngine.ExecStrings(Script); //ShowMessage('Script executed successfully.'); except on E: EPySystemExit do begin // Handle the SystemExit exception ShowMessage('Script terminated by SystemExit.'); end; on E: EPythonError do begin ShowMessage('Python Error: ' + E.Message); end; on E: Exception do begin ShowMessage('Error: ' + E.Message); end; end; end else begin ShowMessage('Script file does not exist: ' + ScriptFilePath); end; finally Script.Free; end; end; procedure TPythonScriptThread.SetEnvironmentVariables; begin SetEnvironmentVariable('VIRTUAL_ENV', PChar(VENV_PATH)); SetEnvironmentVariable('PYTHONHOME', nil); // Unset PYTHONHOME SetEnvironmentVariable('PATH', PChar(VENV_PATH + '\Scripts;' + GetEnvironmentVariable('PATH'))); end; procedure TPythonScriptThread.ConfigurePythonEnvironment; begin MaskFPUExceptions(True); // Ensure the virtual environment paths are correctly set SetEnvironmentVariables; // Configure Python environment FPythonEngine.ExecString(Format( 'import sys; import os; ' + 'sys.path.append(r"%s"); ' + 'sys.path.append(r"%s\Lib\site-packages"); ' + 'sys.path.append(r"%s"); ' + // Add path to photoneo_genicam 'os.environ["VIRTUAL_ENV"] = r"%s"; ' + 'os.environ["PATH"] = r"%s\Scripts;" + os.environ["PATH"]; ' + 'print("sys.path:", sys.path); ' + 'print("VIRTUAL_ENV:", os.environ["VIRTUAL_ENV"]); ' + 'print("PATH:", os.environ["PATH"])', [VENV_PATH, VENV_PATH, PHOTONEO_GENICAM_PATH, VENV_PATH, VENV_PATH] )); end; procedure TPythonScriptThread.Set_DevSerialNumber(Value: String); begin FDevSerialNumber := Value; end; procedure TPythonScriptThread.Set_HorizontalRotation(Value: Double); begin FHorizontalRotation := Value; end; procedure TPythonScriptThread.Set_VerticalRotation(Value: Double); begin FVerticalRotation := Value; end; procedure TPythonScriptThread.Set_ScanZoomFactor(Value: Double); begin FScanZoomFactor := Value; end; // Form stuff: procedure TForm1.nioTimerIntervalChange(Sender: TObject); begin try if(Not nioTimerInterval.Value.IsNan) then FTimeTrigScanInterval := nioTimerInterval.IntegerValue; except FTimeTrigScanInterval := 10; nioTimerInterval.Value := FTimeTrigScanInterval; end; end; procedure TForm1.nioZoomFactorChange(Sender: TObject); begin try if(nioZoomFactor.Value.IsNan) AND (Assigned(PythonScriptThread)) then nioTimerInterval.Value := 200; except end; end; procedure TForm1.Panel1DblClick(Sender: TObject); begin // Reset fields: if(GetKeyState(VK_SHIFT) < 0) then begin nioHorizontalRotation.Value := 360; nioVerticalRotation.Value := 180; nioZoomFactor.Value := 200; end; end; procedure TForm1.FormCreate(Sender: TObject); begin RequestElevationIfNeeded; FTimerTriggeredScan := False; FRelayTriggeredScan := False; Timer1.Interval := LJ_TIMER_INTERVAL; end; procedure TForm1.FormActivate(Sender: TObject); begin if(Not DirectoryExists(PYHARV_HOME)) then begin ShowMessage(PYHARV_HOME + ' doesn''t exist!'); end else begin // Load the Python DLL using the virtual environment's Python executable PythonScriptThread := TPythonScriptThread.Create; Sleep(10); Application.ProcessMessages; Sleep(10); Application.ProcessMessages; if(Assigned(PythonScriptThread)) then begin try FTimeTrigScanInterval := nioTimerInterval.IntegerValue; except on E: Exception do ShowMessage('Failed to load Python DLL: ' + E.Message); end; FTimeTrigScanInterval := nioTimerInterval.IntegerValue; LastTriggerTime := Now; // try to connect to Lab Jack: Initialize_U3HV; // Now Check if its connected: if(Not FLabJackconnected) then begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; end; end; end; end; procedure TForm1.RadioGroup1Click(Sender: TObject); begin case RadioGroup1.ItemIndex of SCAN_MODE_MANUAL: begin FTimerTriggeredScan := False; FRelayTriggeredScan := False; btnScanNow.Enabled := (Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan); nioTimerInterval.Enabled := FTimerTriggeredScan; end; SCAN_MODE_TIMER: begin FTimerTriggeredScan := True; FRelayTriggeredScan := False; btnScanNow.Enabled := (Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan); nioTimerInterval.Enabled := FTimerTriggeredScan; end; SCAN_MODE_RELAY: begin FTimerTriggeredScan := False; FRelayTriggeredScan := True; btnScanNow.Enabled := (Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan); nioTimerInterval.Enabled := FTimerTriggeredScan; end; else begin RadioGroup1.ItemIndex := 0; btnScanNow.Enabled := True; nioTimerInterval.Enabled := False; end; end; end; procedure TForm1.btnScanNowClick(Sender: TObject); begin btnScanNow.Enabled := False; if PythonScriptThread.ScriptRunning then begin ShowMessage('Scan is still busy.'); Exit; end; Application.ProcessMessages; if(Tbutton(Sender).Name = 'btnScanNow') then begin TriggerByUserToScan; end; btnScanNow.Enabled := (Not PythonScriptThread.ScriptRunning); end; procedure TForm1.Timer1Timer(Sender: TObject); begin if(FTimerTriggeredScan OR FRelayTriggeredScan) then begin Timer1.Enabled := False; //Check for Triggers: if(Not FTimerTriggeredScan) then TriggerByRelayToScan; if(Not FRelayTriggeredScan) then TriggerByTimerToScan; Timer1.Enabled := True; end; end; procedure TForm1.TriggerByUserToScan; begin if((Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan)) then begin if(Not PythonScriptThread.RunPythonScript( editDevSerialNumber.Text, nioHorizontalRotation.Value, nioVerticalRotation.Value, nioZoomFactor.Value )) then begin //Error: ShowMessage('Could not call process!'); end; end; end; procedure TForm1.TriggerByTimerToScan; var myCurrentTime: TDateTime; myTriggerTimeElapsed: Double; begin myCurrentTime := Now; // Calculate time elapsed since the last execution of UpdateMassChart myTriggerTimeElapsed := (myCurrentTime - LastTriggerTime) * 86400; // Convert to seconds if(myTriggerTimeElapsed >= FTimeTrigScanInterval) then begin PythonScriptThread.StopPythonScript; LastTriggerTime := myCurrentTime; PythonScriptThread.RunPythonScript( editDevSerialNumber.Text, nioHorizontalRotation.Value, nioVerticalRotation.Value, nioZoomFactor.Value ); end; end; procedure TForm1.TriggerByRelayToScan; var ActiveFileName : String; myActiveFileCheckSum: String; begin // Enable the Print Monitor Button if(FLabJackConnected AND FRelayTriggeredScan AND (Not FTimerTriggeredScan)) then begin // After the first read.....The Monitorlatch is FALSE....it should only be true // again if BOTH voltages are less than 2.0V!!!! // Just get the voltage readings from the Labjack GetAnalogVoltage(0, FLJVoltage_0); GetAnalogVoltage(1, FLJVoltage_1); if ((FLJVoltage_0 < 2.0) and (FLJVoltage_1 < 2.0)) then FRelayMonitorLatch := True; // Update the Relay Lights Status if(FLJVoltage_0 > 2.0) then begin // Use 2V as the trigger voltage level for now //uiRelay_0_Light.Fill.Color := TAlphaColorRec.Lime; FLJRelay_0_ON := True; end else begin //uiRelay_0_Light.Fill.Color := TAlphaColorRec.Crimson; FLJRelay_0_ON := False; end; if(FLJVoltage_1 > 2.0) then begin // Use 2V as the trigger voltage level for now //uiRelay_1_Light.Fill.Color := TAlphaColorRec.Lime; FLJRelay_1_ON := True; end else begin //uiRelay_1_Light.Fill.Color := TAlphaColorRec.Crimson; FLJRelay_1_ON := False; end; // Now let's figure out if we need to take a photo // // The camera will be "inactive" most of the time // Make it active first.... // // then wait for Relay_1 to turn ON // // Take the photo and turn Relay 1 OFF and Turn Relay 0 OFF // if FLJRelay_0_ON then begin // This means the camera is "active" and updating images // // Now let's see if Relay 1 is ON if FLJRelay_1_ON then begin // Here you should execute scan! // if FRelayMonitorLatch then begin PythonScriptThread.StopPythonScript; // Now let's write the file! PythonScriptThread.RunPythonScript( editDevSerialNumber.Text, nioHorizontalRotation.Value, nioVerticalRotation.Value, nioZoomFactor.Value ); end; // Now set the MonitorLatch to False! FRelayMonitorLatch := False; end; end; end; end; end. Here is the python script: #!/usr/bin/env python import sys from pathlib import Path import open3d as o3d import numpy as np from genicam.genapi import NodeMap from harvesters.core import Component2DImage, Harvester from photoneo_genicam.components import enable_components, enabled_components from photoneo_genicam.default_gentl_producer import producer_path from photoneo_genicam.features import enable_software_trigger from photoneo_genicam.pointcloud import create_3d_vector, map_texture from photoneo_genicam.user_set import load_default_user_set from photoneo_genicam.visualizer import render_static def rotate_point_cloud(point_cloud, horizontal_angle_degrees, vertical_angle_degrees): # Convert angles from degrees to radians horizontal_angle_radians = np.deg2rad(horizontal_angle_degrees) vertical_angle_radians = np.deg2rad(vertical_angle_degrees) # Define the rotation matrix for horizontal rotation (around Y-axis) horizontal_rotation_matrix = np.array([ [np.cos(horizontal_angle_radians), 0, np.sin(horizontal_angle_radians)], [0, 1, 0], [-np.sin(horizontal_angle_radians), 0, np.cos(horizontal_angle_radians)] ]) # Define the rotation matrix for vertical rotation (around X-axis) vertical_rotation_matrix = np.array([ [1, 0, 0], [0, np.cos(vertical_angle_radians), -np.sin(vertical_angle_radians)], [0, np.sin(vertical_angle_radians), np.cos(vertical_angle_radians)] ]) # Apply the rotations to the point cloud point_cloud.rotate(horizontal_rotation_matrix, center=(0, 0, 0)) point_cloud.rotate(vertical_rotation_matrix, center=(0, 0, 0)) def render_with_zoom(point_cloud, zoom_factor): vis = o3d.visualization.Visualizer() vis.create_window() vis.add_geometry(point_cloud) # Get the view control and set the zoom level view_control = vis.get_view_control() view_control.set_zoom(zoom_factor) vis.run() vis.destroy_window() def main(device_sn: str, horizontal_angle: float, vertical_angle: float, zoom_factor: float): with Harvester() as h: h.add_file(str(producer_path), check_existence=True, check_validity=True) h.update() with h.create({"serial_number": device_sn}) as ia: features: NodeMap = ia.remote_device.node_map load_default_user_set(features) enable_software_trigger(features) features.Scan3dOutputMode.value = "CalibratedABC_Grid" enable_components(features, ["Intensity", "Range", "Normal"]) ia.start() features.TriggerSoftware.execute() with ia.fetch(timeout=10) as buffer: components = dict(zip(enabled_components(features), buffer.payload.components)) intensity_component: Component2DImage = components["Intensity"] point_cloud_raw: Component2DImage = components["Range"] normal_component: Component2DImage = components["Normal"] point_cloud = o3d.geometry.PointCloud() point_cloud.points = create_3d_vector(point_cloud_raw.data) point_cloud.normals = create_3d_vector(normal_component.data) point_cloud.colors = map_texture(intensity_component) # Rotate the point cloud as specified by the command-line arguments rotate_point_cloud(point_cloud, horizontal_angle, vertical_angle) # Render the point cloud with the specified zoom factor render_with_zoom(point_cloud, zoom_factor) if __name__ == "__main__": # Set default values #device_id = "FJE-061" #horizontal_angle = 360 #vertical_angle = 180 #zoom_factor = 0.1 # Override default values with command-line arguments if provided if len(sys.argv) > 1: device_id = sys.argv[1] if len(sys.argv) > 2: horizontal_angle = float(sys.argv[2]) if len(sys.argv) > 3: vertical_angle = float(sys.argv[3]) if len(sys.argv) > 4: zoom_factor = float(sys.argv[4]) # Run main function with values if len(sys.argv) > 4: main(device_id, horizontal_angle, vertical_angle, zoom_factor) Share this post Link to post
DavidJr. 1 Posted June 3 Using the "ThreadPythonExec" method, I tried this: unit Unit1; interface uses Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.Threading, System.Math, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, PythonEngine, PythonVersions, ShellAPI, SDL_NumIO, SDL_stringl, Vcl.ExtCtrls, LJUDDelphi, Vcl.WinXCtrls, TlHelp32; const PYHARV_HOME = 'C:\Users\printeruser\Desktop\3DScan_Work\photoneo-python-examples\GigEV\harvesters'; VENV_PATH = PYHARV_HOME + '\.venv'; VENV_PYTHON_EXE = VENV_PATH + '\Scripts\python.exe'; PHOTONEO_GENICAM_PATH = PYHARV_HOME + '\advanced\photoneo_genicam'; LJ_TIMER_INTERVAL = 100; SCAN_MODE_MANUAL = 0; SCAN_MODE_TIMER = 1; SCAN_MODE_RELAY = 2; type TForm1 = class(TForm) Panel1: TPanel; nioVerticalRotation: TNumIO2; PythonInputOutput1: TPythonInputOutput; PythonEngine1: TPythonEngine; RadioGroup1: TRadioGroup; editDevSerialNumber: TEdit; nioHorizontalRotation: TNumIO2; nioZoomFactor: TNumIO2; btnScanNow: TButton; lblDeviceSerialNum_Label: TLabel; Timer1: TTimer; nioTimerInterval: TNumIO2; lblLabJackStatus: TLabel; uiRelay_0_Voltage: TLabel; uiRelay_1_Voltage: TLabel; procedure btnScanNowClick(Sender: TObject); procedure FormActivate(Sender: TObject); procedure FormCreate(Sender: TObject); procedure RadioGroup1Click(Sender: TObject); procedure nioTimerIntervalChange(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure nioZoomFactorChange(Sender: TObject); procedure Panel1DblClick(Sender: TObject); private { Private declarations } // LabJack Vars for the class: FLabJackConnected: Boolean; FRelayMonitorLatch: Boolean; FLJRelay_0_ON, FLJRelay_1_ON: Boolean; FLJErrorcode: LJ_ERROR; // LabJack Error Code FLJHandle: LJ_HANDLE; // Handle for LabJack device FLJVoltage_0, FLJVoltage_1 : double; // Timer & TDateTime Trigger: LastTriggerTime: TDateTime; // The rest of th eclass vars: FTimerTriggeredScan: Boolean; FRelayTriggeredScan: Boolean; FTimeTrigScanInterval: Integer; FScanZoomFactor: Double; FScriptRunning: Boolean; FPythonOpen3DPID: DWORD; PyVersion: TPythonVersion; procedure SetEnvironmentVariables; procedure ConfigurePythonEnvironment; function FindPythonOpen3DSubprocess: DWORD; function StopPythonScript: Boolean; function RunPythonScript: Boolean; procedure RequestElevationIfNeeded; function IsUserAnAdmin: Boolean; // Trigger procedures: procedure TriggerByUserToScan; procedure TriggerByTimerToScan; procedure TriggerByRelayToScan; // LabJack: procedure Initialize_U3HV; procedure ErrorHandler(LJErrorcode: LJ_ERROR; LJIteration: longint); procedure SetDACVoltage(Channel: Integer; TheSetVoltage: Double); procedure GetAnalogVoltage(Channel: Integer; var TheVoltage: Double); public { Public declarations } end; var Form1: TForm1; implementation {$R *.dfm} // LabJack: procedure TForm1.Initialize_U3HV; var numChannels, quickSample, longSettling: longint; LabJack_Check : integer; begin // This performs the standard initialization of the U3-HV system // Some initial values LabJack_Check := 0; FLJHandle := 0; numChannels := 16; //Number of AIN channels, 0-16. quickSample := 0; //Set to TRUE for quick AIN sampling. longSettling := 1; //Set to TRUE for extra AIN settling time. // Now let's open the labjack FLJErrorcode := OpenLabJack(LJ_dtU3, LJ_ctUSB, '1', 1, FLJHandle); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); // Now let's reset the configuration FLJErrorcode := ePut(FLJHandle, LJ_ioPIN_CONFIGURATION_RESET, 0, 0, 0); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); //Configure quickSample. FLJErrorcode := ePut(FLJHandle, LJ_ioPUT_CONFIG, LJ_chAIN_RESOLUTION, quickSample, 0); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); //Configure longSettling. FLJErrorcode := ePut(FLJHandle, LJ_ioPUT_CONFIG, LJ_chAIN_SETTLING_TIME, longSettling, 0); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); //Configure the lines as analog. FLJErrorcode := ePut(FLJHandle, LJ_ioPUT_ANALOG_ENABLE_PORT, 0, (IntPower(2, numChannels) - 1), numChannels); ErrorHandler(FLJErrorcode, 0); if FLJErrorcode > 0 then INC(LabJack_Check); if LabJack_Check = 0 then begin lblLabJackStatus.Font.Color := clLime; lblLabJackStatus.Caption := 'LabJack Connected'; end else begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; end; // Let's set the DAC0 and DAC1 to 0.0 Volts at boot-up SetDACVoltage(0, 0.0); SetDACVoltage(1, 0.0); end; procedure TForm1.ErrorHandler(LJErrorcode: LJ_ERROR; LJIteration: longint); var err: array[0..254] of AnsiChar; begin if LJErrorcode <> LJE_NOERROR then begin ErrorToString(LJErrorcode, @err); //StatusUpdate('Error number = ' + IntToStr(lngErrorcode)+ ' Error string = ' + string(err) + ' Iteration = ' + IntToStr(LJIteration)); FLabJackConnected := False; // there was an error! end else begin // LabJack Operation is OK FLabJackConnected := True; end; end; procedure TForm1.SetDACVoltage(Channel: Integer; TheSetVoltage: Double); begin if FLabJackConnected then begin // This uses the global lngHandle to refer to the U3-HV if ABS(TheSetVoltage) < 10 then ePUT(FLJHandle, LJ_ioPUT_DAC, Channel, TheSetVoltage, 0) else begin // Add an error statement if the DAC is set overrange...! lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Connected & Overrange!'; end; end else begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; FLabJackConnected := False; ShowMessage('Problem connecting to LabJack! Check Power and USB connection.'); end; end; procedure TForm1.GetAnalogVoltage(Channel: Integer; var TheVoltage: Double); begin // This uses the global lngHandle to read a voltage from the Analog Channels // // The "LJ_ioGET_AIN" is the integer that determines the operation. Refer to // LJUDDelphi.pas for details if FLabJackConnected then begin eGET(FLJHandle, LJ_ioGET_AIN, Channel, TheVoltage, 0); end else begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; FLabJackConnected := False; ShowMessage('Problem connecting to LabJack! Check Power and USB connection.'); end; // Update the Text readings if Channel = 0 then uiRelay_0_Voltage.Caption := 'Relay 0 Voltage = '+ Strff(FLJVoltage_0, 4, 3)+' V'; if Channel = 1 then uiRelay_1_Voltage.Caption := 'Relay 1 Voltage = '+ Strff(FLJVoltage_1, 4, 3)+' V'; end; // The Rest of the Code: function TForm1.IsUserAnAdmin: Boolean; var hToken: THandle; Elevation: TOKEN_ELEVATION; cbSize: DWORD; begin Result := False; if OpenProcessToken(GetCurrentProcess, TOKEN_QUERY, hToken) then try cbSize := SizeOf(TOKEN_ELEVATION); if GetTokenInformation(hToken, TokenElevation, @Elevation, cbSize, cbSize) then Result := Elevation.TokenIsElevated <> 0; finally CloseHandle(hToken); end; end; procedure TForm1.RequestElevationIfNeeded; var sei: TShellExecuteInfo; exeName: string; begin if not IsUserAnAdmin then begin ZeroMemory(@sei, SizeOf(sei)); sei.cbSize := SizeOf(sei); sei.Wnd := Handle; // Parent window sei.fMask := SEE_MASK_FLAG_DDEWAIT or SEE_MASK_FLAG_NO_UI; sei.lpVerb := 'runas'; // Request elevation exeName := ParamStr(0); sei.lpFile := PChar(exeName); sei.lpParameters := PChar(''); // No parameters sei.nShow := SW_SHOWNORMAL; if not ShellExecuteEx(@sei) then begin ShowMessage('Failed to create elevated process. Error code: ' + IntToStr(GetLastError)); Application.Terminate; // Terminate the unelevated instance end else Application.Terminate; // Terminate the unelevated instance end; end; procedure TForm1.SetEnvironmentVariables; begin SetEnvironmentVariable('VIRTUAL_ENV', PChar(VENV_PATH)); SetEnvironmentVariable('PYTHONHOME', nil); // Unset PYTHONHOME SetEnvironmentVariable('PATH', PChar(VENV_PATH + '\Scripts;' + GetEnvironmentVariable('PATH'))); end; procedure TForm1.ConfigurePythonEnvironment; begin MaskFPUExceptions(True); // Ensure the virtual environment paths are correctly set SetEnvironmentVariables; // Configure Python environment PythonEngine1.ExecString(Format( 'import sys; import os; ' + 'sys.path.append(r"%s"); ' + 'sys.path.append(r"%s\Lib\site-packages"); ' + 'sys.path.append(r"%s"); ' + // Add path to photoneo_genicam 'os.environ["VIRTUAL_ENV"] = r"%s"; ' + 'os.environ["PATH"] = r"%s\Scripts;" + os.environ["PATH"]; ' + 'print("sys.path:", sys.path); ' + 'print("VIRTUAL_ENV:", os.environ["VIRTUAL_ENV"]); ' + 'print("PATH:", os.environ["PATH"])', [VENV_PATH, VENV_PATH, PHOTONEO_GENICAM_PATH, VENV_PATH, VENV_PATH] )); end; procedure TForm1.nioTimerIntervalChange(Sender: TObject); begin try if(Not nioTimerInterval.Value.IsNan) then FTimeTrigScanInterval := nioTimerInterval.IntegerValue; except FTimeTrigScanInterval := 10; nioTimerInterval.Value := FTimeTrigScanInterval; end; end; procedure TForm1.nioZoomFactorChange(Sender: TObject); begin try if(Not nioZoomFactor.Value.IsNan) then FScanZoomFactor := (100 / nioZoomFactor.IntegerValue); except FScanZoomFactor := 0.3; nioTimerInterval.Value := (100/FScanZoomFactor); end; end; procedure TForm1.Panel1DblClick(Sender: TObject); begin // Reset fields: if(GetKeyState(VK_SHIFT) < 0) then begin nioHorizontalRotation.Value := 360; nioVerticalRotation.Value := 180; nioZoomFactor.Value := 200; FScanZoomFactor := (100 / nioZoomFactor.IntegerValue); end; end; procedure TForm1.FormCreate(Sender: TObject); begin RequestElevationIfNeeded; FTimerTriggeredScan := False; FRelayTriggeredScan := False; FScriptRunning := False; Timer1.Interval := LJ_TIMER_INTERVAL; // Load the Python DLL using the virtual environment's Python executable try FTimeTrigScanInterval := nioTimerInterval.IntegerValue; FScanZoomFactor := (100 / nioZoomFactor.IntegerValue); if PythonVersionFromPath(PYHARV_HOME + '\.venv\', PyVersion) then begin PythonEngine1.VenvPythonExe := VENV_PYTHON_EXE; PythonEngine1.SetPythonHome(PYHARV_HOME + '\.venv\'); PyVersion.AssignTo(PythonEngine1); PythonEngine1.LoadDll; // ShowMessage('Python DLL loaded successfully. ' + PythonEngine1.DllName); ConfigurePythonEnvironment; end else begin ShowMessage('No vEnv found!'); end; except on E: Exception do ShowMessage('Failed to load Python DLL: ' + E.Message); end; end; procedure TForm1.FormActivate(Sender: TObject); begin if(Not DirectoryExists(PYHARV_HOME)) then begin ShowMessage(PYHARV_HOME + ' doesn''t exist!'); end; FTimeTrigScanInterval := nioTimerInterval.IntegerValue; LastTriggerTime := Now; // try to connect to Lab Jack: Initialize_U3HV; // Now Check if its connected: if(Not FLabJackconnected) then begin lblLabJackStatus.Font.Color := clRed; lblLabJackStatus.Caption := 'LabJack Disonnected!'; end; end; procedure TForm1.RadioGroup1Click(Sender: TObject); begin case RadioGroup1.ItemIndex of SCAN_MODE_MANUAL: begin FTimerTriggeredScan := False; FRelayTriggeredScan := False; btnScanNow.Enabled := (Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan); nioTimerInterval.Enabled := FTimerTriggeredScan; end; SCAN_MODE_TIMER: begin FTimerTriggeredScan := True; FRelayTriggeredScan := False; btnScanNow.Enabled := (Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan); nioTimerInterval.Enabled := FTimerTriggeredScan; end; SCAN_MODE_RELAY: begin FTimerTriggeredScan := False; FRelayTriggeredScan := True; btnScanNow.Enabled := (Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan); nioTimerInterval.Enabled := FTimerTriggeredScan; end; else begin RadioGroup1.ItemIndex := 0; btnScanNow.Enabled := True; nioTimerInterval.Enabled := False; end; end; end; procedure TForm1.btnScanNowClick(Sender: TObject); begin btnScanNow.Enabled := False; if FScriptRunning then begin ShowMessage('Scan is still busy.'); Exit; end; Application.ProcessMessages; if(Tbutton(Sender).Name = 'btnScanNow') then begin TTask.Run( procedure var t: Integer; begin for t := 0 to 9000 do begin Sleep(10); end; TThread.Synchronize(nil, procedure begin //Py_Exit PythonEngine1.ExecString('exit(0)'); Application.ProcessMessages; end); end); TriggerByUserToScan; end; btnScanNow.Enabled := (Not FScriptRunning); end; procedure TForm1.Timer1Timer(Sender: TObject); begin if(FTimerTriggeredScan OR FRelayTriggeredScan) then begin Timer1.Enabled := False; //Check for Triggers: if(Not FTimerTriggeredScan) then TriggerByRelayToScan; if(Not FRelayTriggeredScan) then TriggerByTimerToScan; Timer1.Enabled := True; end; end; procedure TForm1.TriggerByUserToScan; begin if((Not FTimerTriggeredScan) AND (Not FRelayTriggeredScan)) then begin RunPythonScript; end; end; procedure TForm1.TriggerByTimerToScan; var myCurrentTime: TDateTime; myTriggerTimeElapsed: Double; begin myCurrentTime := Now; // Calculate time elapsed since the last execution of UpdateMassChart myTriggerTimeElapsed := (myCurrentTime - LastTriggerTime) * 86400; // Convert to seconds if(myTriggerTimeElapsed >= FTimeTrigScanInterval) then begin StopPythonScript; LastTriggerTime := myCurrentTime; RunPythonScript; end; end; procedure TForm1.TriggerByRelayToScan; var ActiveFileName : String; myActiveFileCheckSum: String; begin // Enable the Print Monitor Button if(FLabJackConnected AND FRelayTriggeredScan AND (Not FTimerTriggeredScan)) then begin // After the first read.....The Monitorlatch is FALSE....it should only be true // again if BOTH voltages are less than 2.0V!!!! // Just get the voltage readings from the Labjack GetAnalogVoltage(0, FLJVoltage_0); GetAnalogVoltage(1, FLJVoltage_1); if ((FLJVoltage_0 < 2.0) and (FLJVoltage_1 < 2.0)) then FRelayMonitorLatch := True; // Update the Relay Lights Status if(FLJVoltage_0 > 2.0) then begin // Use 2V as the trigger voltage level for now //uiRelay_0_Light.Fill.Color := TAlphaColorRec.Lime; FLJRelay_0_ON := True; end else begin //uiRelay_0_Light.Fill.Color := TAlphaColorRec.Crimson; FLJRelay_0_ON := False; end; if(FLJVoltage_1 > 2.0) then begin // Use 2V as the trigger voltage level for now //uiRelay_1_Light.Fill.Color := TAlphaColorRec.Lime; FLJRelay_1_ON := True; end else begin //uiRelay_1_Light.Fill.Color := TAlphaColorRec.Crimson; FLJRelay_1_ON := False; end; // Now let's figure out if we need to take a photo // // The camera will be "inactive" most of the time // Make it active first.... // // then wait for Relay_1 to turn ON // // Take the photo and turn Relay 1 OFF and Turn Relay 0 OFF // if FLJRelay_0_ON then begin // This means the camera is "active" and updating images // // Now let's see if Relay 1 is ON if FLJRelay_1_ON then begin // Here you should execute scan! // if FRelayMonitorLatch then begin StopPythonScript; // Now let's write the file! RunPythonScript; end; // Now set the MonitorLatch to False! FRelayMonitorLatch := False; end; end; end; end; // Python uses Open3D Library modules: function TForm1.FindPythonOpen3DSubprocess: DWORD; var Snapshot: THandle; ProcessEntry: TProcessEntry32; begin Result := 0; Snapshot := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); if Snapshot <> INVALID_HANDLE_VALUE then try ProcessEntry.dwSize := SizeOf(ProcessEntry); if Process32First(Snapshot, ProcessEntry) then begin repeat if Pos('Open3D', ProcessEntry.szExeFile) > 0 then begin Result := ProcessEntry.th32ProcessID; Break; end; until Not Process32Next(Snapshot, ProcessEntry); end; finally CloseHandle(Snapshot); end; end; // Terminate Python execution: function TForm1.StopPythonScript: Boolean; procedure TerminateProcessByPID(PID: DWORD); var ProcessHandle: THandle; begin ProcessHandle := OpenProcess(PROCESS_TERMINATE, False, PID); if ProcessHandle <> 0 then try TerminateProcess(ProcessHandle, 0); finally CloseHandle(ProcessHandle); end; end; begin Result := False; // Terminate the Open3D subprocess if it exists FPythonOpen3DPID := FindPythonOpen3DSubprocess; if FPythonOpen3DPID <> 0 then TerminateProcessByPID(FPythonOpen3DPID); end; // The Actual Python execution: function TForm1.RunPythonScript: Boolean; var Script: TStringList; ScriptFilePath, DeviceID, HorizontalRotation, VerticalRotation, ZoomFactor: string; begin // Prevent Double Clicking... since the python script needs to complete first before re-running: FScriptRunning := True; Script := TStringList.Create; try // Retrieve the values from the form inputs DeviceID := editDevSerialNumber.Text; HorizontalRotation := nioHorizontalRotation.Value.ToString; VerticalRotation := nioVerticalRotation.Value.ToString; ZoomFactor := FScanZoomFactor.ToString; ScriptFilePath := PYHARV_HOME + '\advanced\GridLoigic_pointcloud_with_normals_and_texture.py'; if FileExists(ScriptFilePath) then begin Script.LoadFromFile(ScriptFilePath); // Insert the sys.path and debug statements Script.Insert(2, 'sys.path.append(r"' + PYHARV_HOME + '\advanced")'); Script.Insert(3, 'print("Starting script execution")'); Script.Insert(4, 'try:'); Script.Insert(5, ' import photoneo_genicam'); Script.Insert(6, ' print("photoneo_genicam imported successfully")'); Script.Insert(7, 'except ImportError as e:'); Script.Insert(8, ' print("Failed to import photoneo_genicam:", str(e))'); Script.Insert(9, ' raise'); // Add the parameters as comments for reference Script.Insert(10, '# Parameters passed from Delphi'); Script.Insert(11, 'device_id = "' + DeviceID + '"'); Script.Insert(12, 'horizontal_angle = ' + HorizontalRotation); Script.Insert(13, 'vertical_angle = ' + VerticalRotation); Script.Insert(14, 'zoom_factor = ' + ZoomFactor); // Replace main function call with parameters //Script.Add('main(device_id, horizontal_angle, vertical_angle, zoom_factor)'); try ThreadPythonExec( procedure begin GetPythonEngine.ExecStrings(Script); end, procedure begin FScriptRunning := False; end, False); //PythonEngine1.ExecStrings(Script); //ShowMessage('Script executed successfully.'); except on E: EPySystemExit do begin // Handle the SystemExit exception ShowMessage('Script terminated by SystemExit.'); end; on E: EPythonError do begin ShowMessage('Python Error: ' + E.Message); end; on E: Exception do begin ShowMessage('Error: ' + E.Message); end; end; end else begin ShowMessage('Script file does not exist: ' + ScriptFilePath); end; finally Script.Free; //PythonEngine1. //if(Not (FTimerTriggeredScan or FRelayTriggeredScan)) then //FScriptRunning := False; end; end; end. Share this post Link to post
pyscripter 689 Posted June 3 Please do not insert such long pieces of code in your script. You could have added the project as an attachment. Please read the relevant info PythonThreads · pyscripter/python4delphi Wiki (github.com) Share this post Link to post
DavidJr. 1 Posted June 3 2 minutes ago, pyscripter said: Please do not insert such long pieces of code in your script. You could have added the project as an attachment. Please read the relevant info PythonThreads · pyscripter/python4delphi Wiki (github.com) I have attached the version of the project that uses the example (to the best of my understanding) ThreadPythonExec: This snippet: // ThreadPythonExec( // procedure // begin // PythonEngine1.ExecStrings(Script); // end, // procedure // begin // FScriptRunning := False; // end, // False); PythonEngine1.ExecStrings(Script); I commented out the ThreadPythonExec and uncommented (the last) line.. and it works fine, but the python app blocks the main UI and I am trying to avoid this. GLDelphiScanner_BKUP.zip Share this post Link to post
pyscripter 689 Posted June 3 (edited) Have you followed the instructions? Quote Preparation When PythonEngine loads the python DLL, the main thread holds the GIL. Before you can run python code in threads you must release the GIL. TPythonThread has two class methods that allow you to release the GIL and then acquire back. class procedure Py_Begin_Allow_Threads; class procedure Py_End_Allow_Threads; Py_Begin_Allow_Threads saves the thread state and releases the GIL, whilst Py_End_Allow_Threads tries to get hold of GIL and restore the previous thread state. You should only call Py_Begin_Allow_Threads if you alread hold the GIL. Otherwise a deadlock will occur. You should call Py_Begin_Allow_Threads in your main thread, after the python DLL was loaded, for example in your FormCreate event handler. Also you can call Py_End_Allow_Threads when you are done executing python in threads. Edited June 3 by pyscripter Share this post Link to post
DavidJr. 1 Posted June 3 I did not, I thought that was actually done using the ThreadPythonExec as well as in this: FTask := TTask.Create( procedure var Py: IPyEngineAndGIL; begin Py := SafePyEngine; Py.PythonEngine.ExecStrings(Script); end); FTask.Start; So no matter what I am required to call Py_Begin_Allow_Threads and Py_End_Allow_Threads? Share this post Link to post
pyscripter 689 Posted June 3 (edited) Dealing with python threads and the Global Interpreter Lock (GIL) is quite tricky. You need to either: Understand and use correctly the python API Initialization, Finalization, and Threads — Python 3.12.3 documentation or Use the high-level encapsulation provided by P4D, following the instructions in https://github.com/pyscripter/python4delphi/wiki/PythonThreads religiously. Otherwise, things can very easily go wrong. Edited June 3 by pyscripter Share this post Link to post
DavidJr. 1 Posted June 4 17 hours ago, pyscripter said: Dealing with python threads and the Global Interpreter Lock (GIL) is quite tricky. You need to either: Understand and use correctly the python API Initialization, Finalization, and Threads — Python 3.12.3 documentation or Use the high-level encapsulation provided by P4D, following the instructions in https://github.com/pyscripter/python4delphi/wiki/PythonThreads religiously. Otherwise, things can very easily go wrong. Thanks. I will. For the time being I am using CreateProcess and calling the script as a commandline and its working fine. I will revisit this later. Share this post Link to post