From 0e6ec6e3ac1aa705659761e1a63e249ea56cc29a Mon Sep 17 00:00:00 2001 From: Pesc0 Date: Tue, 11 Jul 2023 15:56:10 +0200 Subject: [PATCH 1/3] create utils file with get_python_exe --- src/Ext/freecad/CMakeLists.txt | 2 + src/Ext/freecad/utils.py | 63 +++++++++++++++++++ .../DlgSettingsPythonConsole.cpp | 2 + .../DlgSettingsPythonConsole.ui | 50 ++++++++++++++- 4 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/Ext/freecad/utils.py diff --git a/src/Ext/freecad/CMakeLists.txt b/src/Ext/freecad/CMakeLists.txt index 5a8246f67e..2fea55b9ac 100644 --- a/src/Ext/freecad/CMakeLists.txt +++ b/src/Ext/freecad/CMakeLists.txt @@ -27,6 +27,7 @@ endif() configure_file(__init__.py.template ${NAMESPACE_INIT}) configure_file(project_utility.py ${NAMESPACE_DIR}/project_utility.py) configure_file(UiTools.py ${NAMESPACE_DIR}/UiTools.py) +configure_file(utils.py ${NAMESPACE_DIR}/utils.py) if (INSTALL_TO_SITEPACKAGES) SET(SITE_PACKAGE_DIR ${PYTHON_MAIN_DIR}/freecad) @@ -39,6 +40,7 @@ INSTALL( ${NAMESPACE_INIT} project_utility.py UiTools.py + utils.py DESTINATION ${SITE_PACKAGE_DIR} ) diff --git a/src/Ext/freecad/utils.py b/src/Ext/freecad/utils.py new file mode 100644 index 0000000000..01bb923e50 --- /dev/null +++ b/src/Ext/freecad/utils.py @@ -0,0 +1,63 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# *************************************************************************** +# * * +# * Copyright (c) 2022-2023 FreeCAD Project Association * +# * Copyright (c) 2018 Gaël Écorchard * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, but * +# * WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# *************************************************************************** + +import os +import platform +import shutil + +import FreeCAD + + +def get_python_exe() -> str: + """Find Python. In preference order + A) The value of the PythonExecutableForPip user preference + B) The executable located in the same bin directory as FreeCAD and called "python3" + C) The executable located in the same bin directory as FreeCAD and called "python" + D) The result of a shutil search for your system's "python3" executable + E) The result of a shutil search for your system's "python" executable""" + prefs = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/PythonConsole") + python_exe = prefs.GetString("ExternalPythonExecutable", "Not set") + fc_dir = FreeCAD.getHomePath() + if not python_exe or python_exe == "Not set" or not os.path.exists(python_exe): + python_exe = os.path.join(fc_dir, "bin", "python3") + if "Windows" in platform.system(): + python_exe += ".exe" + + if not python_exe or not os.path.exists(python_exe): + python_exe = os.path.join(fc_dir, "bin", "python") + if "Windows" in platform.system(): + python_exe += ".exe" + + if not python_exe or not os.path.exists(python_exe): + python_exe = shutil.which("python3") + + if not python_exe or not os.path.exists(python_exe): + python_exe = shutil.which("python") + + if not python_exe or not os.path.exists(python_exe): + return "" + + python_exe = python_exe.replace("/", os.path.sep) + prefs.SetString("ExternalPythonExecutable", python_exe) + return python_exe diff --git a/src/Gui/PreferencePages/DlgSettingsPythonConsole.cpp b/src/Gui/PreferencePages/DlgSettingsPythonConsole.cpp index 0734266741..87ca89034f 100644 --- a/src/Gui/PreferencePages/DlgSettingsPythonConsole.cpp +++ b/src/Gui/PreferencePages/DlgSettingsPythonConsole.cpp @@ -45,6 +45,7 @@ void DlgSettingsPythonConsole::saveSettings() ui->PythonBlockCursor->onSave(); ui->PythonSaveHistory->onSave(); ui->ProfilerInterval->onSave(); + ui->ExternalPythonExecutable->onSave(); } void DlgSettingsPythonConsole::loadSettings() @@ -53,6 +54,7 @@ void DlgSettingsPythonConsole::loadSettings() ui->PythonBlockCursor->onRestore(); ui->PythonSaveHistory->onRestore(); ui->ProfilerInterval->onRestore(); + ui->ExternalPythonExecutable->onRestore(); } void DlgSettingsPythonConsole::changeEvent(QEvent* event) diff --git a/src/Gui/PreferencePages/DlgSettingsPythonConsole.ui b/src/Gui/PreferencePages/DlgSettingsPythonConsole.ui index b7f2ac20b6..0c5292d921 100644 --- a/src/Gui/PreferencePages/DlgSettingsPythonConsole.ui +++ b/src/Gui/PreferencePages/DlgSettingsPythonConsole.ui @@ -11,13 +11,13 @@ - Python console + General - Settings + Console @@ -111,6 +111,47 @@ horizontal space in Python console + + + Other + + + + + + Path to external Python executable (optional): + + + + + + + + 0 + 0 + + + + + 300 + 0 + + + + Used for package installation with pip and debugging with debugpy. Autodetected if needed and not specified. + + + ExternalPythonExecutable + + + PythonConsole + + + + + + + Qt::Vertical @@ -135,6 +176,11 @@ horizontal space in Python console Gui::PrefSpinBox QSpinBox
Gui/PrefWidgets.h
+ + + Gui::PrefFileChooser + QWidget +
Gui/PrefWidgets.h
From be6ed4670bc16c980577ef2c2317ddb687ce470e Mon Sep 17 00:00:00 2001 From: Pesc0 Date: Tue, 11 Jul 2023 15:57:48 +0200 Subject: [PATCH 2/3] addon manager: use get_python_exe from utils file --- src/Mod/AddonManager/AddonManagerOptions.ui | 32 ----------------- .../addonmanager_dependency_installer.py | 4 ++- src/Mod/AddonManager/addonmanager_devmode.py | 3 +- .../addonmanager_preferences_defaults.json | 1 - .../AddonManager/addonmanager_utilities.py | 34 ------------------- .../manage_python_dependencies.py | 3 +- 6 files changed, 7 insertions(+), 70 deletions(-) diff --git a/src/Mod/AddonManager/AddonManagerOptions.ui b/src/Mod/AddonManager/AddonManagerOptions.ui index 7460e591ef..c935646f72 100644 --- a/src/Mod/AddonManager/AddonManagerOptions.ui +++ b/src/Mod/AddonManager/AddonManagerOptions.ui @@ -270,38 +270,6 @@ installed addons will be checked for available updates
- - - - Path to Python executable (optional): - - - - - - - - 0 - 0 - - - - - 300 - 0 - - - - The path to the Python executable for package installation with pip. Autodetected if needed and not specified. - - - PythonExecutableForPip - - - Addons - - - diff --git a/src/Mod/AddonManager/addonmanager_dependency_installer.py b/src/Mod/AddonManager/addonmanager_dependency_installer.py index ccff230cf1..466809e2de 100644 --- a/src/Mod/AddonManager/addonmanager_dependency_installer.py +++ b/src/Mod/AddonManager/addonmanager_dependency_installer.py @@ -27,6 +27,8 @@ import os import subprocess from typing import List +from freecad.utils import get_python_exe + import addonmanager_freecad_interface as fci from addonmanager_pyside_interface import QObject, Signal, is_interruption_requested @@ -170,7 +172,7 @@ class DependencyInstaller(QObject): def _get_python(self) -> str: """Wrap Python access so test code can mock it.""" - python_exe = utils.get_python_exe() + python_exe = get_python_exe() if not python_exe: self.no_python_exe.emit() return python_exe diff --git a/src/Mod/AddonManager/addonmanager_devmode.py b/src/Mod/AddonManager/addonmanager_devmode.py index 87aae2d00b..860b9ab934 100644 --- a/src/Mod/AddonManager/addonmanager_devmode.py +++ b/src/Mod/AddonManager/addonmanager_devmode.py @@ -29,6 +29,7 @@ import subprocess import FreeCAD import FreeCADGui +from freecad.utils import get_python_exe from PySide.QtWidgets import ( QFileDialog, @@ -618,7 +619,7 @@ class DeveloperMode: FreeCAD.Console.PrintMessage( translate("AddonsInstaller", "Attempting to install Vermin from PyPi") + "...\n" ) - python_exe = utils.get_python_exe() + python_exe = get_python_exe() vendor_path = os.path.join(FreeCAD.getUserAppDataDir(), "AdditionalPythonPackages") if not os.path.exists(vendor_path): os.makedirs(vendor_path) diff --git a/src/Mod/AddonManager/addonmanager_preferences_defaults.json b/src/Mod/AddonManager/addonmanager_preferences_defaults.json index 2602c566f7..99a953bc55 100644 --- a/src/Mod/AddonManager/addonmanager_preferences_defaults.json +++ b/src/Mod/AddonManager/addonmanager_preferences_defaults.json @@ -25,7 +25,6 @@ "PrimaryAddonsSubmoduleURL": "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/.gitmodules", "ProxyUrl": "", - "PythonExecutableForPip": "Not set", "RemoteIconCacheURL": "https://addons.freecad.org/icon_cache.zip", "SelectedAddon": "", "ShowBranchSwitcher": false, diff --git a/src/Mod/AddonManager/addonmanager_utilities.py b/src/Mod/AddonManager/addonmanager_utilities.py index 5d71023d34..9ceabc2e9a 100644 --- a/src/Mod/AddonManager/addonmanager_utilities.py +++ b/src/Mod/AddonManager/addonmanager_utilities.py @@ -345,40 +345,6 @@ def is_float(element: Any) -> bool: return False -def get_python_exe() -> str: - """Find Python. In preference order - A) The value of the PythonExecutableForPip user preference - B) The executable located in the same bin directory as FreeCAD and called "python3" - C) The executable located in the same bin directory as FreeCAD and called "python" - D) The result of a shutil search for your system's "python3" executable - E) The result of a shutil search for your system's "python" executable""" - prefs = fci.ParamGet("User parameter:BaseApp/Preferences/Addons") - python_exe = prefs.GetString("PythonExecutableForPip", "Not set") - fc_dir = fci.DataPaths().home_dir - if not python_exe or python_exe == "Not set" or not os.path.exists(python_exe): - python_exe = os.path.join(fc_dir, "bin", "python3") - if "Windows" in platform.system(): - python_exe += ".exe" - - if not python_exe or not os.path.exists(python_exe): - python_exe = os.path.join(fc_dir, "bin", "python") - if "Windows" in platform.system(): - python_exe += ".exe" - - if not python_exe or not os.path.exists(python_exe): - python_exe = shutil.which("python3") - - if not python_exe or not os.path.exists(python_exe): - python_exe = shutil.which("python") - - if not python_exe or not os.path.exists(python_exe): - return "" - - python_exe = python_exe.replace("/", os.path.sep) - prefs.SetString("PythonExecutableForPip", python_exe) - return python_exe - - def get_pip_target_directory(): # Get the default location to install new pip packages major, minor, _ = platform.python_version_tuple() diff --git a/src/Mod/AddonManager/manage_python_dependencies.py b/src/Mod/AddonManager/manage_python_dependencies.py index 24108d571a..4b42cd9298 100644 --- a/src/Mod/AddonManager/manage_python_dependencies.py +++ b/src/Mod/AddonManager/manage_python_dependencies.py @@ -36,6 +36,7 @@ from typing import Dict, List, Tuple import FreeCAD import FreeCADGui +from freecad.utils import get_python_exe from PySide import QtCore, QtGui, QtWidgets import addonmanager_utilities as utils @@ -89,7 +90,7 @@ def call_pip(args) -> List[str]: """Tries to locate the appropriate Python executable and run pip with version checking disabled. Fails if Python can't be found or if pip is not installed.""" - python_exe = utils.get_python_exe() + python_exe = get_python_exe() pip_failed = False if python_exe: call_args = [python_exe, "-m", "pip", "--disable-pip-version-check"] From 6fe6cbe4b766c3be2627a78eae18a422c28cd634 Mon Sep 17 00:00:00 2001 From: Pesc0 Date: Fri, 17 Nov 2023 01:07:54 +0100 Subject: [PATCH 3/3] Upgrade debugger to debugpy --- requirements.txt | 2 +- src/Gui/RemoteDebugger.py | 15 +++++++++++---- src/Gui/RemoteDebugger.ui | 10 ---------- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/requirements.txt b/requirements.txt index 42bf9c4acd..42135681ea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ opencamlib==2023.1.11 packaging==23.0 Pivy==0.6.8 ply==3.11 -ptvsd==4.3.2 +debugpy==1.6.7 pyNastran==1.3.4 pyshp==2.3.1 PySide2==5.15.2.1 diff --git a/src/Gui/RemoteDebugger.py b/src/Gui/RemoteDebugger.py index 28353d96b5..637d757074 100644 --- a/src/Gui/RemoteDebugger.py +++ b/src/Gui/RemoteDebugger.py @@ -25,6 +25,7 @@ import FreeCAD as App import FreeCADGui as Gui from PySide import QtGui +from freecad.utils import get_python_exe class RemoteDebugger(): def __init__(self, parent=None): @@ -46,11 +47,17 @@ class RemoteDebugger(): elif index == 1: # VS code address = self.dialog.lineEditAddress.text() port = self.dialog.spinBoxPort.value() - redirect = self.dialog.checkRedirectOutput.isChecked() - import ptvsd - ptvsd.enable_attach(address=(address, port), redirect_output=redirect) - ptvsd.wait_for_attach() + import debugpy + + # get_python_exe is needed because debugpy needs a python interpreter to work. + # It does not have to be FC embedded interpreter. + # By default it attempts to use Freecad's PID mistaking it for python. + # https://github.com/microsoft/debugpy/issues/262 + debugpy.configure(python=get_python_exe()) + debugpy.listen((address, port)) + debugpy.wait_for_client() + except Exception as e: QtGui.QMessageBox.warning(self.dialog, "Failed to attach", str(e)) diff --git a/src/Gui/RemoteDebugger.ui b/src/Gui/RemoteDebugger.ui index 382eb04393..f40327375a 100644 --- a/src/Gui/RemoteDebugger.ui +++ b/src/Gui/RemoteDebugger.ui @@ -89,16 +89,6 @@ - - - - Redirect output - - - true - - -