From 779e1efa55a83443eb7d25d99f5cc0cb70c316e8 Mon Sep 17 00:00:00 2001 From: Furgo <148809153+furgo16@users.noreply.github.com> Date: Sat, 1 Mar 2025 21:23:52 +0100 Subject: [PATCH 01/31] Start: add option to show only FreeCAD native files in custom folder --- src/Mod/Start/App/CustomFolderModel.cpp | 7 +++++++ src/Mod/Start/App/CustomFolderModel.h | 1 + 2 files changed, 8 insertions(+) diff --git a/src/Mod/Start/App/CustomFolderModel.cpp b/src/Mod/Start/App/CustomFolderModel.cpp index bd7036e2e1..6225349039 100644 --- a/src/Mod/Start/App/CustomFolderModel.cpp +++ b/src/Mod/Start/App/CustomFolderModel.cpp @@ -41,6 +41,8 @@ CustomFolderModel::CustomFolderModel(QObject* parent) _customFolderDirectory = QDir(QString::fromStdString(parameterGroup->GetASCII("CustomFolder", ""))); + + _showOnlyFCStd = parameterGroup->GetBool("ShowOnlyFCStd", false); } void CustomFolderModel::loadCustomFolder() @@ -52,6 +54,11 @@ void CustomFolderModel::loadCustomFolder() "BaseApp/Preferences/Mod/Start/CustomFolder: cannot read custom folder %s\n", _customFolderDirectory.absolutePath().toStdString().c_str()); } + + if (_showOnlyFCStd) { + _customFolderDirectory.setNameFilters(QStringList() << QStringLiteral("*.FCStd")); + } + auto entries = _customFolderDirectory.entryList(QDir::Filter::Files | QDir::Filter::Readable, QDir::SortFlag::Name); for (const auto& entry : entries) { diff --git a/src/Mod/Start/App/CustomFolderModel.h b/src/Mod/Start/App/CustomFolderModel.h index d4c132708d..3901e1e01d 100644 --- a/src/Mod/Start/App/CustomFolderModel.h +++ b/src/Mod/Start/App/CustomFolderModel.h @@ -46,6 +46,7 @@ public: private: QDir _customFolderDirectory; + bool _showOnlyFCStd; // Show only FreeCAD files }; } // namespace Start From cf2e0e06b9496e7aa403064fc04309d0d4b48957 Mon Sep 17 00:00:00 2001 From: Furgo <148809153+furgo16@users.noreply.github.com> Date: Sun, 2 Mar 2025 07:39:55 +0100 Subject: [PATCH 02/31] Start: add option to show only FCStd files to the preferences dialog --- src/Mod/Start/Gui/DlgStartPreferences.ui | 52 +++++++++++++++----- src/Mod/Start/Gui/DlgStartPreferencesImp.cpp | 2 + 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/Mod/Start/Gui/DlgStartPreferences.ui b/src/Mod/Start/Gui/DlgStartPreferences.ui index 2dcf6df394..20f0e5bd1a 100644 --- a/src/Mod/Start/Gui/DlgStartPreferences.ui +++ b/src/Mod/Start/Gui/DlgStartPreferences.ui @@ -20,6 +20,20 @@ Contents + + + + Show only FreeCAD files in additional folder + + + + + + + Show examples folder contents + + + @@ -27,6 +41,22 @@ + + + + An optional custom folder to be displayed on the Start page. + + + Gui::FileChooser::Directory + + + CustomFolder + + + Mod/Start + + + @@ -49,29 +79,25 @@ - - + + - An optional custom folder to be displayed on the Start page. + If the additional folder contents should include only .FCStd files - - Gui::FileChooser::Directory + + Qt::RightToLeft + + + - CustomFolder + ShowOnlyFCStd Mod/Start - - - - Show examples folder contents - - - diff --git a/src/Mod/Start/Gui/DlgStartPreferencesImp.cpp b/src/Mod/Start/Gui/DlgStartPreferencesImp.cpp index 0d842d1b23..84b17fae2d 100644 --- a/src/Mod/Start/Gui/DlgStartPreferencesImp.cpp +++ b/src/Mod/Start/Gui/DlgStartPreferencesImp.cpp @@ -53,6 +53,7 @@ void DlgStartPreferencesImp::saveSettings() ui->fileChooserCustomFolder->onSave(); ui->checkBoxShowExamples->onSave(); ui->checkBoxCloseAfterLoading->onSave(); + ui->checkBoxShowOnlyFCStd->onSave(); } void DlgStartPreferencesImp::loadSettings() @@ -60,6 +61,7 @@ void DlgStartPreferencesImp::loadSettings() ui->fileChooserCustomFolder->onRestore(); ui->checkBoxShowExamples->onRestore(); ui->checkBoxCloseAfterLoading->onRestore(); + ui->checkBoxShowOnlyFCStd->onRestore(); } /** From 1b755ab29d0e3d3f0b68823e81d466508c010f8c Mon Sep 17 00:00:00 2001 From: Alfredo Monclus Date: Sun, 2 Mar 2025 05:49:08 -0300 Subject: [PATCH 03/31] start: fileview use scrollbars as needed if for whatever reason the viewport failed to resize let qt add the scrollbars --- src/Mod/Start/Gui/FileCardView.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Mod/Start/Gui/FileCardView.cpp b/src/Mod/Start/Gui/FileCardView.cpp index 5b2ab5687d..343a644d9c 100644 --- a/src/Mod/Start/Gui/FileCardView.cpp +++ b/src/Mod/Start/Gui/FileCardView.cpp @@ -39,7 +39,7 @@ FileCardView::FileCardView(QWidget* parent) sizePolicy.setHeightForWidth(true); setSizePolicy(sizePolicy); setHorizontalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); - setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAlwaysOff); + setVerticalScrollBarPolicy(Qt::ScrollBarPolicy::ScrollBarAsNeeded); setViewMode(QListView::ViewMode::IconMode); setFlow(QListView::Flow::LeftToRight); setResizeMode(QListView::ResizeMode::Adjust); From 5d97c20cc732a5d4ef1d92f4ae94d9c9a1e87767 Mon Sep 17 00:00:00 2001 From: Joao Matos Date: Sun, 16 Feb 2025 14:15:00 +0000 Subject: [PATCH 04/31] CI: Refactor generic whitespace checks linting setup. --- .github/workflows/sub_lint.yml | 107 ++++----------------- tools/lint/generic_checks.py | 165 +++++++++++++++++++++++++++++++++ tools/lint/utils.py | 95 +++++++++++++++++++ 3 files changed, 278 insertions(+), 89 deletions(-) create mode 100755 tools/lint/generic_checks.py create mode 100644 tools/lint/utils.py diff --git a/.github/workflows/sub_lint.yml b/.github/workflows/sub_lint.yml index ab28e819da..11bb736d37 100644 --- a/.github/workflows/sub_lint.yml +++ b/.github/workflows/sub_lint.yml @@ -276,103 +276,32 @@ jobs: if: inputs.checkLineendings && always() continue-on-error: ${{ inputs.lineendingsFailSilent }} run: | - lineendings=0 - for file in ${{ inputs.changedFiles }} - do - # Check for DOS or MAC line endings - if [[ $(file -b $file) =~ "with CR" ]] - then - echo "::warning file=$file,title=$file::File has non Unix line endings" - echo "$file has non Unix line endings" >> ${{ env.logdir }}lineendings.log - ((lineendings=lineendings+1)) - fi - done - echo "Found $lineendings line ending errors" - # Write the report - if [ $lineendings -gt 0 ] - then - echo "
:information_source: Found $lineendings problems with line endings" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}lineendings.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo ":heavy_check_mark: No line ending problem found " >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $lineendings -eq 0 ] + python3 tools/lint/generic_checks.py \ + --lineendings-check \ + --files "${{ inputs.changedFiles }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" \ + --log-dir "${{ env.logdir }}" + - name: Check for trailing whitespaces if: inputs.checkWhitespace && always() continue-on-error: ${{ inputs.whitespaceFailSilent }} run: | - whitespaceErrors=0 - exclude="*[.md]" - for file in ${{ inputs.changedFiles }} - do - # Check for trailing whitespaces - grep -nIHE --exclude="$exclude" " $" $file | sed -e "s/$/<-- trailing whitespace/" >> ${{ env.logdir }}whitespace.log || true - done - # Write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}whitespace.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/grepMatcherWarning.json" - cat ${{ env.logdir }}whitespace.log - echo "::remove-matcher owner=grepMatcher-warning::" - whitespaceErrors=$(wc -l < ${{ env.logdir }}whitespace.log) - fi - echo "Found $whitespaceErrors whitespace errors" - # Write the report - if [ $whitespaceErrors -gt 0 ] - then - echo "
:information_source: Found $whitespaceErrors trailing whitespace" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}whitespace.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo ":heavy_check_mark: No trailing whitespace found " >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $whitespaceErrors -eq 0 ] + python3 tools/lint/generic_checks.py \ + --whitespace-check \ + --files "${{ inputs.changedFiles }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" \ + --log-dir "${{ env.logdir }}" + - name: Check for Tab usage if: inputs.checkTabs && always() continue-on-error: ${{ inputs.tabsFailSilent }} run: | - tabErrors=0 - exclude="*[.md]" - # Check for Tab usage - for file in ${{ steps.changed-files.outputs.all_changed_files }} - do - grep -nIHE --exclude="$exclude" $'\t' $file | sed -e "s/$/ <-- contains tab/" >> ${{ env.logdir }}tab.log || true - done - # Write the Log to the console with the Problem Matchers - if [ -f ${{ env.logdir }}tab.log ] - then - echo "::add-matcher::${{ runner.workspace }}/FreeCAD/.github/problemMatcher/grepMatcherWarning.json" - cat ${{ env.logdir }}tab.log - echo "::remove-matcher owner=grepMatcher-warning::" - tabErrors=$(wc -l < ${{ env.logdir }}tab.log) - fi - echo "Found $tabErrors tab errors" - # Write the report - if [ $tabErrors -gt 0 ]; then - echo "
:information_source: Found $tabErrors tabs, better to use spaces" >> ${{env.reportdir}}${{ env.reportfilename }} - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - cat ${{ env.logdir }}tab.log >> ${{env.reportdir}}${{ env.reportfilename }} - echo '```' >> ${{env.reportdir}}${{ env.reportfilename }} - echo "
" >> ${{env.reportdir}}${{ env.reportfilename }} - else - echo ":heavy_check_mark: No tabs found" >> ${{env.reportdir}}${{ env.reportfilename }} - fi - echo "" >> ${{env.reportdir}}${{ env.reportfilename }} - # Exit the step with appropriate code - [ $tabErrors -eq 0 ] - # Run Python lints + python3 tools/lint/generic_checks.py \ + --tabs-check \ + --files "${{ inputs.changedFiles }}" \ + --report-file "${{ env.reportdir }}${{ env.reportfilename }}" \ + --log-dir "${{ env.logdir }}" + - name: Pylint if: inputs.checkPylint && inputs.changedPythonFiles != '' && always() continue-on-error: ${{ inputs.pylintFailSilent }} diff --git a/tools/lint/generic_checks.py b/tools/lint/generic_checks.py new file mode 100755 index 0000000000..25f1a2d794 --- /dev/null +++ b/tools/lint/generic_checks.py @@ -0,0 +1,165 @@ +import argparse +import glob +import os +from utils import ( + add_common_arguments, + init_environment, + write_file, + emit_problem_matchers, +) + + +def check_line_endings(file_paths): + """Check if files have non-Unix (CRLF or CR) line endings. + + Returns a dict mapping file path to an issue description. + """ + issues = {} + for path in file_paths: + try: + with open(path, "rb") as f: + content = f.read() + if b"\r\n" in content or (b"\r" in content and b"\n" not in content): + issues[path] = "File has non-Unix line endings." + except Exception as e: + issues[path] = f"Error reading file: {e}" + return issues + + +def check_trailing_whitespace(file_paths): + """Check for trailing whitespace in files. + + Returns a dict mapping file paths to a list of line numbers with trailing whitespace. + """ + issues = {} + for path in file_paths: + try: + with open(path, "r", encoding="utf-8") as f: + lines = f.readlines() + error_lines = [] + for idx, line in enumerate(lines, start=1): + if line.rstrip("\n") != line.rstrip(): + error_lines.append(idx) + if error_lines: + issues[path] = error_lines + except Exception as e: + issues[path] = f"Error reading file: {e}" + return issues + + +def check_tabs(file_paths): + """Check for usage of tab characters in files. + + Returns a dict mapping file paths to a list of line numbers containing tabs. + """ + issues = {} + for path in file_paths: + try: + with open(path, "r", encoding="utf-8") as f: + lines = f.readlines() + tab_lines = [] + for idx, line in enumerate(lines, start=1): + if "\t" in line: + tab_lines.append(idx) + if tab_lines: + issues[path] = tab_lines + except Exception as e: + issues[path] = f"Error reading file: {e}" + return issues + + +def format_report(section_title, issues): + """Format a report section with a Markdown details block.""" + report = [] + if issues: + count = len(issues) + report.append( + f"
:information_source: Found {count} issue(s) in {section_title}\n" + ) + report.append("```") + for file, details in issues.items(): + report.append(f"{file}: {details}") + report.append("```\n
\n") + else: + report.append(f":heavy_check_mark: No issues found in {section_title}\n") + return "\n".join(report) + + +def main(): + parser = argparse.ArgumentParser(description="Run formatting lint checks.") + add_common_arguments(parser) + parser.add_argument( + "--lineendings-check", + action="store_true", + help="Enable check for non-Unix line endings.", + ) + parser.add_argument( + "--whitespace-check", + action="store_true", + help="Enable check for trailing whitespace.", + ) + parser.add_argument( + "--tabs-check", action="store_true", help="Enable check for tab characters." + ) + args = parser.parse_args() + init_environment(args) + + file_list = glob.glob(args.files, recursive=True) + file_list = [f for f in file_list if os.path.isfile(f)] + + report_sections = [] + + # Check non-Unix line endings. + if args.lineendings_check: + le_issues = check_line_endings(file_list) + for file, detail in le_issues.items(): + print(f"::warning file={file},title={file}::{detail}") + report_sections.append(format_report("Non-Unix Line Endings", le_issues)) + + # Check trailing whitespace. + if args.whitespace_check: + ws_issues = check_trailing_whitespace(file_list) + if ws_issues: + ws_output_lines = [] + for file, details in ws_issues.items(): + if isinstance(details, list): + for line in details: + ws_output_lines.append(f"{file}:{line}: trailing whitespace") + else: + ws_output_lines.append(f"{file}: {details}") + ws_output = "\n".join(ws_output_lines) + + ws_log_file = os.path.join(args.log_dir, "whitespace.log") + write_file(ws_log_file, ws_output) + emit_problem_matchers( + ws_log_file, "grepMatcherWarning.json", "grepMatcher-warning" + ) + report_sections.append(format_report("Trailing Whitespace", ws_issues)) + + # Check tab usage. + if args.tabs_check: + tab_issues = check_tabs(file_list) + if tab_issues: + tab_output_lines = [] + for file, details in tab_issues.items(): + if isinstance(details, list): + for line in details: + tab_output_lines.append(f"{file}:{line}: contains tab") + else: + tab_output_lines.append(f"{file}: {details}") + tab_output = "\n".join(tab_output_lines) + + tab_log_file = os.path.join(args.log_dir, "tabs.log") + write_file(tab_log_file, tab_output) + emit_problem_matchers( + tab_log_file, "grepMatcherWarning.json", "grepMatcher-warning" + ) + report_sections.append(format_report("Tab Usage", tab_issues)) + + report_content = "\n".join(report_sections) + write_file(args.report_file, report_content) + print("Lint report generated at:", args.report_file) + + +if __name__ == "__main__": + main() diff --git a/tools/lint/utils.py b/tools/lint/utils.py new file mode 100644 index 0000000000..26cc4186a5 --- /dev/null +++ b/tools/lint/utils.py @@ -0,0 +1,95 @@ +#!/usr/bin/env python3 +import os +import sys +import logging +import subprocess +import argparse +from typing import Tuple + + +def run_command(cmd, check=False) -> Tuple[str, str, int]: + """ + Run a command using subprocess.run and return stdout, stderr, and exit code. + """ + try: + result = subprocess.run( + cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=check, text=True + ) + return result.stdout, result.stderr, result.returncode + except subprocess.CalledProcessError as e: + return e.stdout, e.stderr, e.returncode + + +def setup_logger(verbose: bool): + """ + Setup the logging level based on the verbose flag. + """ + level = logging.DEBUG if verbose else logging.INFO + logging.basicConfig(level=level, format="%(levelname)s: %(message)s") + + +def write_file(file_path: str, content: str): + """Write content to the specified file.""" + with open(file_path, "w", encoding="utf-8") as f: + f.write(content) + logging.info("Wrote file: %s", file_path) + + +def append_file(file_path: str, content: str): + """Append content to the specified file.""" + with open(file_path, "a", encoding="utf-8") as f: + f.write(content + "\n") + logging.info("Appended content to file: %s", file_path) + + +def emit_problem_matchers(log_path: str, matcher_filename: str, remove_owner: str): + """ + Emit GitHub Actions problem matcher commands using the given matcher file. + + This function will: + 1. Check if the log file exists. + 2. Use the RUNNER_WORKSPACE environment variable to construct the matcher path. + 3. Print the add-matcher command, then the log content, then the remove-matcher command. + """ + if os.path.isfile(log_path): + runner_workspace = os.getenv("RUNNER_WORKSPACE") + matcher_path = os.path.join( + runner_workspace, "FreeCAD", ".github", "problemMatcher", matcher_filename + ) + print(f"::add-matcher::{matcher_path}") + with open(log_path, "r", encoding="utf-8") as f: + sys.stdout.write(f.read()) + print(f"::remove-matcher owner={remove_owner}::") + + +def add_common_arguments(parser: argparse.ArgumentParser) -> argparse.ArgumentParser: + """ + Add common command-line arguments shared between tools. + """ + parser.add_argument( + "--files", + required=True, + help="A space-separated list or glob pattern of files to check.", + ) + parser.add_argument( + "--log-dir", required=True, help="Directory where log files will be written." + ) + parser.add_argument( + "--report-file", + required=True, + help="Path to the Markdown report file to append results.", + ) + parser.add_argument("--verbose", action="store_true", help="Enable verbose output.") + return parser + + +def init_environment(args: argparse.Namespace): + """ + Perform common initialization tasks: + - Set up logging. + - Create the log directory. + - Create the directory for the report file. + """ + setup_logger(args.verbose) + os.makedirs(args.log_dir, exist_ok=True) + os.makedirs(os.path.dirname(args.report_file), exist_ok=True) From 5e72c0afe508a698cadb209af190102b12774005 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Matos?= Date: Sun, 2 Mar 2025 19:47:55 +0000 Subject: [PATCH 05/31] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Benjamin Bræstrup Sayoc --- tools/lint/generic_checks.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tools/lint/generic_checks.py b/tools/lint/generic_checks.py index 25f1a2d794..b7eb19675f 100755 --- a/tools/lint/generic_checks.py +++ b/tools/lint/generic_checks.py @@ -35,11 +35,8 @@ def check_trailing_whitespace(file_paths): for path in file_paths: try: with open(path, "r", encoding="utf-8") as f: - lines = f.readlines() - error_lines = [] - for idx, line in enumerate(lines, start=1): - if line.rstrip("\n") != line.rstrip(): - error_lines.append(idx) + lines = files.read().splitlines() + error_lines = [idx if line.endswith(" ") for idx, line in enumerate(lines, start=1)] if error_lines: issues[path] = error_lines except Exception as e: @@ -57,10 +54,7 @@ def check_tabs(file_paths): try: with open(path, "r", encoding="utf-8") as f: lines = f.readlines() - tab_lines = [] - for idx, line in enumerate(lines, start=1): - if "\t" in line: - tab_lines.append(idx) + tab_lines = [idx if "\t" in line for idx, line in enumerate(lines, start=1)] if tab_lines: issues[path] = tab_lines except Exception as e: From b59cd794274c8c007b2ef16c3a06fecf30c0aef1 Mon Sep 17 00:00:00 2001 From: Maxim Moskalets Date: Mon, 3 Mar 2025 22:30:38 +0300 Subject: [PATCH 06/31] Add support of compile/link jobs pooling --- .../InitializeFreeCADBuildOptions.cmake | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake b/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake index 6b19043f77..6e8c8e42b0 100644 --- a/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake +++ b/cMake/FreeCAD_Helpers/InitializeFreeCADBuildOptions.cmake @@ -13,6 +13,8 @@ macro(InitializeFreeCADBuildOptions) option(FREECAD_USE_FREETYPE "Builds the features using FreeType libs" ON) option(FREECAD_BUILD_DEBIAN "Prepare for a build of a Debian package" OFF) option(FREECAD_CHECK_PIVY "Check for pivy version using Python at build time" ON) + option(FREECAD_PARALLEL_COMPILE_JOBS "Compilation jobs pool size to fit memory limitations.") + option(FREECAD_PARALLEL_LINK_JOBS "Linkage jobs pool size to fit memory limitations.") option(BUILD_WITH_CONDA "Set ON if you build FreeCAD with conda" OFF) option(BUILD_DYNAMIC_LINK_PYTHON "If OFF extension-modules do not link against python-libraries" ON) option(INSTALL_TO_SITEPACKAGES "If ON the freecad root namespace (python) is installed into python's site-packages" ON) @@ -206,4 +208,22 @@ macro(InitializeFreeCADBuildOptions) "Please choose another build directory! Or disable the option BUILD_FORCE_DIRECTORY.") endif() endif() + + if(FREECAD_PARALLEL_COMPILE_JOBS) + if(CMAKE_GENERATOR MATCHES "Ninja") + set_property(GLOBAL APPEND PROPERTY JOB_POOLS compile_job_pool=${FREECAD_PARALLEL_COMPILE_JOBS}) + set(CMAKE_JOB_POOL_COMPILE compile_job_pool) + else() + message(WARNING "Job pooling is only available with Ninja generators.") + endif() + endif() + + if(FREECAD_PARALLEL_LINK_JOBS) + if(CMAKE_GENERATOR MATCHES "Ninja") + set_property(GLOBAL APPEND PROPERTY JOB_POOLS link_job_pool=${FREECAD_PARALLEL_LINK_JOBS}) + set(CMAKE_JOB_POOL_LINK link_job_pool) + else() + message(WARNING "Job pooling is only available with Ninja generators.") + endif() + endif() endmacro(InitializeFreeCADBuildOptions) From 819faf71edc62051cf7966ede16e3820b68b99d6 Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 11:34:27 +1000 Subject: [PATCH 07/31] DRY --- src/App/Application.cpp | 76 ++++++++++++----------------------------- 1 file changed, 22 insertions(+), 54 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 507b063f51..aefe2ac054 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -394,64 +394,32 @@ void Application::setupPythonTypes() // clang-format on } -// clang-format off +/* + * Define custom Python exception types + */ void Application::setupPythonException(PyObject* module) { - // Define custom Python exception types - // - Base::PyExc_FC_GeneralError = PyErr_NewException("Base.FreeCADError", PyExc_RuntimeError, nullptr); - Py_INCREF(Base::PyExc_FC_GeneralError); - PyModule_AddObject(module, "FreeCADError", Base::PyExc_FC_GeneralError); + auto setup = [&module, str {"Base."}](const std::string& ename, auto pyExcType) { + auto exception = PyErr_NewException((str + ename).c_str(), pyExcType, nullptr); + Py_INCREF(exception); + PyModule_AddObject(module, ename.c_str(), exception); + return exception; + }; - Base::PyExc_FC_FreeCADAbort = PyErr_NewException("Base.FreeCADAbort", PyExc_BaseException, nullptr); - Py_INCREF(Base::PyExc_FC_FreeCADAbort); - PyModule_AddObject(module, "FreeCADAbort", Base::PyExc_FC_FreeCADAbort); - - Base::PyExc_FC_XMLBaseException = PyErr_NewException("Base.XMLBaseException", PyExc_Exception, nullptr); - Py_INCREF(Base::PyExc_FC_XMLBaseException); - PyModule_AddObject(module, "XMLBaseException", Base::PyExc_FC_XMLBaseException); - - Base::PyExc_FC_XMLParseException = PyErr_NewException("Base.XMLParseException", Base::PyExc_FC_XMLBaseException, nullptr); - Py_INCREF(Base::PyExc_FC_XMLParseException); - PyModule_AddObject(module, "XMLParseException", Base::PyExc_FC_XMLParseException); - - Base::PyExc_FC_XMLAttributeError = PyErr_NewException("Base.XMLAttributeError", Base::PyExc_FC_XMLBaseException, nullptr); - Py_INCREF(Base::PyExc_FC_XMLAttributeError); - PyModule_AddObject(module, "XMLAttributeError", Base::PyExc_FC_XMLAttributeError); - - Base::PyExc_FC_UnknownProgramOption = PyErr_NewException("Base.UnknownProgramOption", PyExc_BaseException, nullptr); - Py_INCREF(Base::PyExc_FC_UnknownProgramOption); - PyModule_AddObject(module, "UnknownProgramOption", Base::PyExc_FC_UnknownProgramOption); - - Base::PyExc_FC_BadFormatError = PyErr_NewException("Base.BadFormatError", Base::PyExc_FC_GeneralError, nullptr); - Py_INCREF(Base::PyExc_FC_BadFormatError); - PyModule_AddObject(module, "BadFormatError", Base::PyExc_FC_BadFormatError); - - Base::PyExc_FC_BadGraphError = PyErr_NewException("Base.BadGraphError", Base::PyExc_FC_GeneralError, nullptr); - Py_INCREF(Base::PyExc_FC_BadGraphError); - PyModule_AddObject(module, "BadGraphError", Base::PyExc_FC_BadGraphError); - - Base::PyExc_FC_ExpressionError = PyErr_NewException("Base.ExpressionError", Base::PyExc_FC_GeneralError, nullptr); - Py_INCREF(Base::PyExc_FC_ExpressionError); - PyModule_AddObject(module, "ExpressionError", Base::PyExc_FC_ExpressionError); - - Base::PyExc_FC_ParserError = PyErr_NewException("Base.ParserError", Base::PyExc_FC_GeneralError, nullptr); - Py_INCREF(Base::PyExc_FC_ParserError); - PyModule_AddObject(module, "ParserError", Base::PyExc_FC_ParserError); - - Base::PyExc_FC_CADKernelError = PyErr_NewException("Base.CADKernelError", Base::PyExc_FC_GeneralError, nullptr); - Py_INCREF(Base::PyExc_FC_CADKernelError); - PyModule_AddObject(module, "CADKernelError", Base::PyExc_FC_CADKernelError); - - Base::PyExc_FC_PropertyError = PyErr_NewException("Base.PropertyError", PyExc_AttributeError, nullptr); - Py_INCREF(Base::PyExc_FC_PropertyError); - PyModule_AddObject(module, "PropertyError", Base::PyExc_FC_PropertyError); - - Base::PyExc_FC_AbortIOException = PyErr_NewException("Base.PyExc_FC_AbortIOException", PyExc_BaseException, nullptr); - Py_INCREF(Base::PyExc_FC_AbortIOException); - PyModule_AddObject(module, "AbortIOException", Base::PyExc_FC_AbortIOException); + PyExc_FC_GeneralError = setup("FreeCADError", PyExc_RuntimeError); + PyExc_FC_FreeCADAbort = setup("FreeCADAbort", PyExc_BaseException); + PyExc_FC_XMLBaseException = setup("XMLBaseException", PyExc_Exception); + PyExc_FC_XMLParseException = setup("XMLParseException", PyExc_FC_XMLBaseException); + PyExc_FC_XMLAttributeError = setup("XMLAttributeError", PyExc_FC_XMLBaseException); + PyExc_FC_UnknownProgramOption = setup("UnknownProgramOption", PyExc_BaseException); + PyExc_FC_BadFormatError = setup("BadFormatError", PyExc_FC_GeneralError); + PyExc_FC_BadGraphError = setup("BadGraphError", PyExc_FC_GeneralError); + PyExc_FC_ExpressionError = setup("ExpressionError", PyExc_FC_GeneralError); + PyExc_FC_ParserError = setup("ParserError", PyExc_FC_GeneralError); + PyExc_FC_CADKernelError = setup("CADKernelError", PyExc_FC_GeneralError); + PyExc_FC_PropertyError = setup("PropertyError", PyExc_AttributeError); + PyExc_FC_AbortIOException = setup("AbortIOException", PyExc_BaseException); } -// clang-format on //************************************************************************** // Interface From c3eee2dc997d274fe0fedb4ad25a45bd6ac5b3ce Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:31:27 +1000 Subject: [PATCH 08/31] Simplify expression --- src/App/Application.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index aefe2ac054..0fe509646e 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -2668,10 +2668,10 @@ void Application::initConfig(int argc, char ** argv) _pConsoleObserverFile = nullptr; // Banner =========================================================== - if (!(mConfig["RunMode"] == "Cmd")) { + if (mConfig["RunMode"] != "Cmd") { // Remove banner if FreeCAD is invoked via the -c command as regular // Python interpreter - if (!(mConfig["Verbose"] == "Strict")) + if (mConfig["Verbose"] != "Strict") Base::Console().Message("%s %s, Libs: %s.%s.%s%sR%s\n%s", mConfig["ExeName"].c_str(), mConfig["ExeVersion"].c_str(), @@ -2797,7 +2797,7 @@ void Application::initApplication() new Base::ScriptProducer( "FreeCADTest", FreeCADTest ); // creating the application - if (!(mConfig["Verbose"] == "Strict")) + if (mConfig["Verbose"] != "Strict") Base::Console().Log("Create Application\n"); Application::_pcSingleton = new Application(mConfig); @@ -3013,7 +3013,7 @@ void Application::LoadParameters() _pcUserParamMngr->SetSerializer(new ParameterSerializer(mConfig["UserParameter"])); try { - if (_pcSysParamMngr->LoadOrCreateDocument() && !(mConfig["Verbose"] == "Strict")) { + if (_pcSysParamMngr->LoadOrCreateDocument() && mConfig["Verbose"] != "Strict") { // Configuration file optional when using as Python module if (!Py_IsInitialized()) { Base::Console().Warning(" Parameter does not exist, writing initial one\n"); @@ -3032,7 +3032,7 @@ void Application::LoadParameters() } try { - if (_pcUserParamMngr->LoadOrCreateDocument() && !(mConfig["Verbose"] == "Strict")) { + if (_pcUserParamMngr->LoadOrCreateDocument() && mConfig["Verbose"] != "Strict") { // The user parameter file doesn't exist. When an alternative parameter file is offered // this will be used. std::map::iterator it = mConfig.find("UserParameterTemplate"); From 2ab03306cac8c78806f2e30395cb1d3dab088c3e Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:33:03 +1000 Subject: [PATCH 09/31] Use auto --- src/App/Application.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 0fe509646e..de2094f275 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -519,7 +519,7 @@ Document* Application::newDocument(const char * proposedName, const char * propo bool Application::closeDocument(const char* name) { - map::iterator pos = DocMap.find( name ); + auto pos = DocMap.find( name ); if (pos == DocMap.end()) // no such document return false; @@ -1214,7 +1214,7 @@ int Application::checkLinkDepth(int depth, MessageOption option) } if (depth > _objCount + 2) { - const char *msg = "Link recursion limit reached. " + auto msg = "Link recursion limit reached. " "Please check for cyclic reference."; switch (option) { case MessageOption::Quiet: @@ -2408,7 +2408,7 @@ void processProgramOptions(const variables_map& vm, std::map Mods = vm["module-path"].as< vector >(); + auto Mods = vm["module-path"].as< vector >(); string temp; for (const auto & It : Mods) temp += It + ";"; @@ -2426,7 +2426,7 @@ void processProgramOptions(const variables_map& vm, std::map Paths = vm["python-path"].as< vector >(); + auto Paths = vm["python-path"].as< vector >(); for (const auto & It : Paths) Base::Interpreter().addPythonPath(It.c_str()); } @@ -2442,7 +2442,7 @@ void processProgramOptions(const variables_map& vm, std::map files(vm["input-file"].as< vector >()); + auto files(vm["input-file"].as< vector >()); int OpenFileCount=0; for (const auto & It : files) { @@ -2510,7 +2510,7 @@ void processProgramOptions(const variables_map& vm, std::map(); + auto configKey = vm["get-config"].as(); std::stringstream str; std::map::iterator pos; pos = mConfig.find(configKey); @@ -2522,7 +2522,7 @@ void processProgramOptions(const variables_map& vm, std::map configKeyValue = vm["set-config"].as< std::vector >(); + auto configKeyValue = vm["set-config"].as< std::vector >(); for (const auto& it : configKeyValue) { auto pos = it.find('='); if (pos != std::string::npos) { @@ -2621,9 +2621,9 @@ void Application::initConfig(int argc, char ** argv) // because the (external) interpreter is already initialized. // Therefore we use a workaround as described in https://stackoverflow.com/a/57019607 - PyObject *sysModules = PyImport_GetModuleDict(); + PyObject* sysModules = PyImport_GetModuleDict(); - const char *moduleName = "FreeCAD"; + auto moduleName = "FreeCAD"; PyImport_AddModule(moduleName); ApplicationMethods = Application::Methods; PyObject *pyModule = init_freecad_module(); @@ -2933,8 +2933,8 @@ void Application::processCmdLineFiles() } } - const std::map& cfg = Application::Config(); - std::map::const_iterator it = cfg.find("SaveFile"); + const std::map& cfg = Application::Config(); + auto it = cfg.find("SaveFile"); if (it != cfg.end()) { std::string output = it->second; output = Base::Tools::escapeEncodeFilename(output); @@ -3035,7 +3035,7 @@ void Application::LoadParameters() if (_pcUserParamMngr->LoadOrCreateDocument() && mConfig["Verbose"] != "Strict") { // The user parameter file doesn't exist. When an alternative parameter file is offered // this will be used. - std::map::iterator it = mConfig.find("UserParameterTemplate"); + auto it = mConfig.find("UserParameterTemplate"); if (it != mConfig.end()) { QString path = QString::fromUtf8(it->second.c_str()); if (QDir(path).isRelative()) { From 78696bdaee535bbd1a1faad6981c8e53873bc566 Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:42:29 +1000 Subject: [PATCH 10/31] Join declaration and assignment --- src/App/Application.cpp | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index de2094f275..40bec112e5 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -554,9 +554,8 @@ void Application::closeAllDocuments() App::Document* Application::getDocument(const char *Name) const { - std::map::const_iterator pos; - pos = DocMap.find(Name); + const auto pos = DocMap.find(Name); if (pos == DocMap.end()) return nullptr; @@ -1056,10 +1055,7 @@ void Application::setActiveDocument(const char* Name) return; } - std::map::iterator pos; - pos = DocMap.find(Name); - - if (pos != DocMap.end()) { + if (const auto pos = DocMap.find(Name); pos != DocMap.end()) { setActiveDocument(pos->second); } else { @@ -3484,12 +3480,11 @@ std::string Application::FindHomePath(const char* call) // FreeCAD shared library. if (!Py_IsInitialized()) { uint32_t sz = 0; - char *buf; - _NSGetExecutablePath(NULL, &sz); //function only returns "sz" if first arg is to small to hold value - buf = new char[++sz]; + _NSGetExecutablePath( + nullptr, &sz); // function only returns "sz" if first arg is to small to hold value - if (_NSGetExecutablePath(buf, &sz) == 0) { + if (const auto buf = new char[++sz]; _NSGetExecutablePath(buf, &sz) == 0) { char resolved[PATH_MAX]; char* path = realpath(buf, resolved); delete [] buf; From ee849b634969f723a96216c0e751cace59e9654e Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:44:54 +1000 Subject: [PATCH 11/31] C++ style cast instead of C --- src/App/Application.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 40bec112e5..613002c026 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -1233,7 +1233,7 @@ std::set Application::getLinksTo( if(!obj) { for(auto &v : DocMap) { v.second->getLinksTo(links,obj,options,maxCount); - if(maxCount && (int)links.size()>=maxCount) + if(maxCount && static_cast(links.size())>=maxCount) break; } } else { @@ -1241,7 +1241,7 @@ std::set Application::getLinksTo( for(auto o : obj->getInList()) { if(o && o->isAttachedToDocument() && docs.insert(o->getDocument()).second) { o->getDocument()->getLinksTo(links,obj,options,maxCount); - if(maxCount && (int)links.size()>=maxCount) + if(maxCount && static_cast(links.size())>=maxCount) break; } } @@ -2800,8 +2800,8 @@ void Application::initApplication() // set up Unit system default ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/Units"); - Base::UnitsApi::setSchema((Base::UnitSystem)hGrp->GetInt("UserSchema",0)); - Base::UnitsApi::setDecimals(hGrp->GetInt("Decimals", Base::UnitsApi::getDecimals())); + UnitsApi::setSchema(static_cast(hGrp->GetInt("UserSchema", 0))); + UnitsApi::setDecimals(hGrp->GetInt("Decimals", UnitsApi::getDecimals())); // In case we are using fractional inches, get user setting for min unit int denom = hGrp->GetInt("FracInch", Base::QuantityFormat::getDefaultDenominator()); From 4bfe65b48cbc5882c4ba8cf909cc16f92950ae9e Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:46:10 +1000 Subject: [PATCH 12/31] Declaration hides previous --- src/App/Application.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 613002c026..0779767728 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -955,8 +955,8 @@ Document* Application::openDocumentPrivate(const char * FileName, // objects. To partially solve this problem, we do not // close and reopen the document immediately here, but // add it to _pendingDocsReopen to delay reloading. - for(auto obj : doc->getObjects()) - objNames.emplace_back(obj->getNameInDocument()); + for(auto obj2 : doc->getObjects()) + objNames.emplace_back(obj2->getNameInDocument()); _pendingDocMap[doc->FileName.getValue()] = std::move(objNames); break; } @@ -2368,10 +2368,10 @@ void parseProgramOptions(int ac, char ** av, const string& exe, variables_map& v // Split the file content char_separator sep(" \n\r"); tokenizer > tok(ss.str(), sep); - vector args; - copy(tok.begin(), tok.end(), back_inserter(args)); + vector args2; + copy(tok.begin(), tok.end(), back_inserter(args2)); // Parse the file and store the options - store( boost::program_options::command_line_parser(args). + store( boost::program_options::command_line_parser(args2). options(cmdline_options).positional(p).extra_parser(Util::customSyntax).run(), vm); } } From 31825c28aa26562305c98482eb11428e75729ccc Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 12:57:34 +1000 Subject: [PATCH 13/31] Else after return --- src/App/Application.cpp | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 0779767728..ef114ad7d9 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -971,10 +971,13 @@ Document* Application::openDocumentPrivate(const char * FileName, } } - if(!isMainDoc) + if (!isMainDoc) { return nullptr; - else if(doc) + } + + if (doc) { return doc; + } } std::string name; @@ -1266,10 +1269,8 @@ ParameterManager & Application::GetUserParameter() ParameterManager * Application::GetParameterSet(const char* sName) const { auto it = mpcPramManager.find(sName); - if ( it != mpcPramManager.end() ) - return it->second; - else - return nullptr; + + return it != mpcPramManager.end() ? it->second : nullptr; } const std::map> & @@ -3174,13 +3175,8 @@ void getOldDataLocation(std::map& mConfig, std::vector< */ QString findUserHomePath(const QString& userHome) { - if (userHome.isEmpty()) { - return getUserHome(); - } - else { - return userHome; - } -} + return userHome.isEmpty() ? getUserHome() : userHome; + } /*! * \brief findPath From 8ff4b1b0183138e7fbab8871b5992cf00ddab00a Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:07:56 +1000 Subject: [PATCH 14/31] Redundant c_str() --- src/App/Application.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index ef114ad7d9..eae485173c 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -1163,7 +1163,7 @@ std::string Application::getResourceDir() { #ifdef RESOURCEDIR // #6892: Conda may inject null characters => remove them - std::string path = std::string(RESOURCEDIR).c_str(); + auto path = std::string(RESOURCEDIR); path += PATHSEP; QDir dir(QString::fromStdString(path)); if (dir.isAbsolute()) @@ -1178,7 +1178,7 @@ std::string Application::getLibraryDir() { #ifdef LIBRARYDIR // #6892: Conda may inject null characters => remove them - std::string path = std::string(LIBRARYDIR).c_str(); + auto path = std::string(LIBRARYDIR); QDir dir(QString::fromStdString(path)); if (dir.isAbsolute()) return path; @@ -1192,7 +1192,7 @@ std::string Application::getHelpDir() { #ifdef DOCDIR // #6892: Conda may inject null characters => remove them - std::string path = std::string(DOCDIR).c_str(); + auto path = std::string(DOCDIR); path += PATHSEP; QDir dir(QString::fromStdString(path)); if (dir.isAbsolute()) @@ -1311,7 +1311,7 @@ Base::Reference Application::GetParameterGroupByPath(const char* cName.erase(0,pos+1); // test if name is valid - auto It = mpcPramManager.find(cTemp.c_str()); + auto It = mpcPramManager.find(cTemp); if (It == mpcPramManager.end()) throw Base::ValueError("Application::GetParameterGroupByPath() unknown parameter set name specified"); From 4811be9881a026d1bb3fd950746b466126447a17 Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:11:23 +1000 Subject: [PATCH 15/31] Reserve vector size --- src/App/Application.cpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index eae485173c..583b5537e6 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -577,6 +577,7 @@ const char * Application::getDocumentName(const App::Document* doc) const std::vector Application::getDocuments() const { std::vector docs; + docs.reserve(DocMap.size()); for (const auto & it : DocMap) docs.push_back(it.second); return docs; @@ -1378,8 +1379,10 @@ std::vector Application::getImportModules(const char* Type) const std::vector Application::getImportModules() const { std::vector modules; - for (const auto & it : _mImportTypes) + modules.reserve(_mImportTypes.size()); + for (const auto& it : _mImportTypes) { modules.push_back(it.module); + } std::sort(modules.begin(), modules.end()); modules.erase(std::unique(modules.begin(), modules.end()), modules.end()); return modules; @@ -1501,8 +1504,10 @@ std::vector Application::getExportModules(const char* Type) const std::vector Application::getExportModules() const { std::vector modules; - for (const auto & it : _mExportTypes) + modules.reserve(_mExportTypes.size()); + for (const auto& it : _mExportTypes) { modules.push_back(it.module); + } std::sort(modules.begin(), modules.end()); modules.erase(std::unique(modules.begin(), modules.end()), modules.end()); return modules; From 3ddf2fe67bf2b3b0d11d7156b32180066e2f5bc5 Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:17:34 +1000 Subject: [PATCH 16/31] Don't use endl --- src/App/Application.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 583b5537e6..2557a45a43 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -1823,11 +1823,11 @@ void printBacktrace(size_t skip=0) std::stringstream str; if (status == 0) { void* offset = (void*)((char*)callstack[i] - (char*)info.dli_saddr); - str << "#" << (i-skip) << " " << callstack[i] << " in " << demangled << " from " << info.dli_fname << "+" << offset << std::endl; + str << "#" << (i-skip) << " " << callstack[i] << " in " << demangled << " from " << info.dli_fname << "+" << offset << '\n'; free(demangled); } else { - str << "#" << (i-skip) << " " << symbols[i] << std::endl; + str << "#" << (i-skip) << " " << symbols[i] << '\n'; } // cannot directly print to cerr when using --write-log @@ -1856,19 +1856,19 @@ void segmentation_fault_handler(int sig) #else switch (sig) { case SIGSEGV: - std::cerr << "Illegal storage access..." << std::endl; + std::cerr << "Illegal storage access..." << '\n'; #if !defined(_DEBUG) throw Base::AccessViolation("Illegal storage access! Please save your work under a new file name and restart the application!"); #endif break; case SIGABRT: - std::cerr << "Abnormal program termination..." << std::endl; + std::cerr << "Abnormal program termination..." << '\n'; #if !defined(_DEBUG) throw Base::AbnormalProgramTermination("Break signal occurred"); #endif break; default: - std::cerr << "Unknown error occurred..." << std::endl; + std::cerr << "Unknown error occurred..." << '\n'; break; } #endif // FC_OS_LINUX @@ -1876,12 +1876,12 @@ void segmentation_fault_handler(int sig) void unhandled_exception_handler() { - std::cerr << "Terminating..." << std::endl; + std::cerr << "Terminating..." << '\n'; } void unexpection_error_handler() { - std::cerr << "Unexpected error occurred..." << std::endl; + std::cerr << "Unexpected error occurred..." << '\n'; // try to throw an exception and give the user chance to save their work #if !defined(_DEBUG) throw Base::AbnormalProgramTermination("Unexpected error occurred! Please save your work under a new file name and restart the application!"); @@ -2340,21 +2340,21 @@ void parseProgramOptions(int ac, char ** av, const string& exe, variables_map& v } catch (const std::exception& e) { std::stringstream str; - str << e.what() << endl << endl << visible << endl; + str << e.what() << '\n' << '\n' << visible << '\n'; throw Base::UnknownProgramOption(str.str()); } catch (...) { std::stringstream str; - str << "Wrong or unknown option, bailing out!" << endl << endl << visible << endl; + str << "Wrong or unknown option, bailing out!" << '\n' << '\n' << visible << '\n'; throw Base::UnknownProgramOption(str.str()); } if (vm.count("help")) { std::stringstream str; - str << exe << endl << endl; - str << "For a detailed description see https://www.freecad.org/wiki/Start_up_and_Configuration" << endl<() << "'" << endl; + << vm["response-file"].as() << "'" << '\n'; throw Base::UnknownProgramOption(str.str()); } // Read the whole file into a string @@ -2387,7 +2387,7 @@ void processProgramOptions(const variables_map& vm, std::mapsecond; } - str << std::endl; + str << '\n'; throw Base::ProgramInformation(str.str()); } From 035d789ffa477c1643350f54b8630626bcd62c1e Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:24:20 +1000 Subject: [PATCH 17/31] Narrowing conversion --- src/App/Application.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 2557a45a43..c530a30524 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -2717,12 +2717,12 @@ void Application::initConfig(int argc, char ** argv) #ifdef FC_DEBUG if (v.second>=0) { hasDefault = true; - Base::Console().SetDefaultLogLevel(v.second); + Base::Console().SetDefaultLogLevel(static_cast(v.second)); } #endif } else { - *Base::Console().GetLogLevel(v.first.c_str()) = v.second; + *Base::Console().GetLogLevel(v.first.c_str()) = static_cast(v.second); } } @@ -2807,10 +2807,10 @@ void Application::initApplication() ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath ("User parameter:BaseApp/Preferences/Units"); UnitsApi::setSchema(static_cast(hGrp->GetInt("UserSchema", 0))); - UnitsApi::setDecimals(hGrp->GetInt("Decimals", UnitsApi::getDecimals())); + UnitsApi::setDecimals(static_cast(hGrp->GetInt("Decimals", UnitsApi::getDecimals()))); // In case we are using fractional inches, get user setting for min unit - int denom = hGrp->GetInt("FracInch", Base::QuantityFormat::getDefaultDenominator()); + int denom = static_cast(hGrp->GetInt("FracInch", Base::QuantityFormat::getDefaultDenominator())); Base::QuantityFormat::setDefaultDenominator(denom); From 091f73f09f214f974eda84640db0922769067700 Mon Sep 17 00:00:00 2001 From: bofdahof <172177156+bofdahof@users.noreply.github.com> Date: Tue, 24 Dec 2024 13:29:36 +1000 Subject: [PATCH 18/31] Calling static via instance --- src/App/Application.cpp | 72 ++++++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index c530a30524..980f719c24 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -302,12 +302,12 @@ void Application::setupPythonTypes() // NOTE: To finish the initialization of our own type objects we must // call PyType_Ready, otherwise we run into a segmentation fault, later on. // This function is responsible for adding inherited slots from a type's base class. - Base::Interpreter().addType(&Base::VectorPy::Type, pAppModule, "Vector"); - Base::Interpreter().addType(&Base::MatrixPy::Type, pAppModule, "Matrix"); - Base::Interpreter().addType(&Base::BoundBoxPy::Type, pAppModule, "BoundBox"); - Base::Interpreter().addType(&Base::PlacementPy::Type, pAppModule, "Placement"); - Base::Interpreter().addType(&Base::RotationPy::Type, pAppModule, "Rotation"); - Base::Interpreter().addType(&Base::AxisPy::Type, pAppModule, "Axis"); + Base::InterpreterSingleton::addType(&Base::VectorPy::Type, pAppModule, "Vector"); + Base::InterpreterSingleton::addType(&Base::MatrixPy::Type, pAppModule, "Matrix"); + Base::InterpreterSingleton::addType(&Base::BoundBoxPy::Type, pAppModule, "BoundBox"); + Base::InterpreterSingleton::addType(&Base::PlacementPy::Type, pAppModule, "Placement"); + Base::InterpreterSingleton::addType(&Base::RotationPy::Type, pAppModule, "Rotation"); + Base::InterpreterSingleton::addType(&Base::AxisPy::Type, pAppModule, "Axis"); // Note: Create an own module 'Base' which should provide the python // binding classes from the base module. At a later stage we should @@ -324,39 +324,39 @@ void Application::setupPythonTypes() // Python types - Base::Interpreter().addType(&Base::VectorPy ::Type,pBaseModule,"Vector"); - Base::Interpreter().addType(&Base::MatrixPy ::Type,pBaseModule,"Matrix"); - Base::Interpreter().addType(&Base::BoundBoxPy ::Type,pBaseModule,"BoundBox"); - Base::Interpreter().addType(&Base::PlacementPy ::Type,pBaseModule,"Placement"); - Base::Interpreter().addType(&Base::RotationPy ::Type,pBaseModule,"Rotation"); - Base::Interpreter().addType(&Base::AxisPy ::Type,pBaseModule,"Axis"); - Base::Interpreter().addType(&Base::CoordinateSystemPy::Type,pBaseModule,"CoordinateSystem"); - Base::Interpreter().addType(&Base::TypePy ::Type,pBaseModule,"TypeId"); - Base::Interpreter().addType(&Base::PrecisionPy ::Type,pBaseModule,"Precision"); + Base::InterpreterSingleton::addType(&Base::VectorPy ::Type,pBaseModule,"Vector"); + Base::InterpreterSingleton::addType(&Base::MatrixPy ::Type,pBaseModule,"Matrix"); + Base::InterpreterSingleton::addType(&Base::BoundBoxPy ::Type,pBaseModule,"BoundBox"); + Base::InterpreterSingleton::addType(&Base::PlacementPy ::Type,pBaseModule,"Placement"); + Base::InterpreterSingleton::addType(&Base::RotationPy ::Type,pBaseModule,"Rotation"); + Base::InterpreterSingleton::addType(&Base::AxisPy ::Type,pBaseModule,"Axis"); + Base::InterpreterSingleton::addType(&Base::CoordinateSystemPy::Type,pBaseModule,"CoordinateSystem"); + Base::InterpreterSingleton::addType(&Base::TypePy ::Type,pBaseModule,"TypeId"); + Base::InterpreterSingleton::addType(&Base::PrecisionPy ::Type,pBaseModule,"Precision"); - Base::Interpreter().addType(&App::MaterialPy::Type, pAppModule, "Material"); - Base::Interpreter().addType(&App::MetadataPy::Type, pAppModule, "Metadata"); + Base::InterpreterSingleton::addType(&MaterialPy::Type, pAppModule, "Material"); + Base::InterpreterSingleton::addType(&MetadataPy::Type, pAppModule, "Metadata"); - Base::Interpreter().addType(&App::MeasureManagerPy::Type, pAppModule, "MeasureManager"); + Base::InterpreterSingleton::addType(&MeasureManagerPy::Type, pAppModule, "MeasureManager"); - Base::Interpreter().addType(&App::StringHasherPy::Type, pAppModule, "StringHasher"); - Base::Interpreter().addType(&App::StringIDPy::Type, pAppModule, "StringID"); + Base::InterpreterSingleton::addType(&StringHasherPy::Type, pAppModule, "StringHasher"); + Base::InterpreterSingleton::addType(&StringIDPy::Type, pAppModule, "StringID"); // Add document types - Base::Interpreter().addType(&App::PropertyContainerPy::Type, pAppModule, "PropertyContainer"); - Base::Interpreter().addType(&App::ExtensionContainerPy::Type, pAppModule, "ExtensionContainer"); - Base::Interpreter().addType(&App::DocumentPy::Type, pAppModule, "Document"); - Base::Interpreter().addType(&App::DocumentObjectPy::Type, pAppModule, "DocumentObject"); - Base::Interpreter().addType(&App::DocumentObjectGroupPy::Type, pAppModule, "DocumentObjectGroup"); - Base::Interpreter().addType(&App::GeoFeaturePy::Type, pAppModule, "GeoFeature"); + Base::InterpreterSingleton::addType(&PropertyContainerPy::Type, pAppModule, "PropertyContainer"); + Base::InterpreterSingleton::addType(&ExtensionContainerPy::Type, pAppModule, "ExtensionContainer"); + Base::InterpreterSingleton::addType(&DocumentPy::Type, pAppModule, "Document"); + Base::InterpreterSingleton::addType(&DocumentObjectPy::Type, pAppModule, "DocumentObject"); + Base::InterpreterSingleton::addType(&DocumentObjectGroupPy::Type, pAppModule, "DocumentObjectGroup"); + Base::InterpreterSingleton::addType(&GeoFeaturePy::Type, pAppModule, "GeoFeature"); // Add extension types - Base::Interpreter().addType(&App::ExtensionPy::Type, pAppModule, "Extension"); - Base::Interpreter().addType(&App::DocumentObjectExtensionPy::Type, pAppModule, "DocumentObjectExtension"); - Base::Interpreter().addType(&App::GroupExtensionPy::Type, pAppModule, "GroupExtension"); - Base::Interpreter().addType(&App::GeoFeatureGroupExtensionPy::Type, pAppModule, "GeoFeatureGroupExtension"); - Base::Interpreter().addType(&App::OriginGroupExtensionPy::Type, pAppModule, "OriginGroupExtension"); - Base::Interpreter().addType(&App::LinkBaseExtensionPy::Type, pAppModule, "LinkBaseExtension"); + Base::InterpreterSingleton::addType(&ExtensionPy::Type, pAppModule, "Extension"); + Base::InterpreterSingleton::addType(&DocumentObjectExtensionPy::Type, pAppModule, "DocumentObjectExtension"); + Base::InterpreterSingleton::addType(&GroupExtensionPy::Type, pAppModule, "GroupExtension"); + Base::InterpreterSingleton::addType(&GeoFeatureGroupExtensionPy::Type, pAppModule, "GeoFeatureGroupExtension"); + Base::InterpreterSingleton::addType(&OriginGroupExtensionPy::Type, pAppModule, "OriginGroupExtension"); + Base::InterpreterSingleton::addType(&LinkBaseExtensionPy::Type, pAppModule, "LinkBaseExtension"); //insert Base and Console Py_INCREF(pBaseModule); @@ -377,19 +377,19 @@ void Application::setupPythonTypes() nullptr, nullptr, nullptr, nullptr }; PyObject* pUnitsModule = PyModule_Create(&UnitsModuleDef); - Base::Interpreter().addType(&Base::QuantityPy ::Type,pUnitsModule,"Quantity"); + Base::InterpreterSingleton::addType(&Base::QuantityPy ::Type,pUnitsModule,"Quantity"); // make sure to set the 'nb_true_divide' slot - Base::Interpreter().addType(&Base::UnitPy ::Type,pUnitsModule,"Unit"); + Base::InterpreterSingleton::addType(&Base::UnitPy ::Type,pUnitsModule,"Unit"); Py_INCREF(pUnitsModule); PyModule_AddObject(pAppModule, "Units", pUnitsModule); Base::ProgressIndicatorPy::init_type(); - Base::Interpreter().addType(Base::ProgressIndicatorPy::type_object(), + Base::InterpreterSingleton::addType(Base::ProgressIndicatorPy::type_object(), pBaseModule,"ProgressIndicator"); Base::Vector2dPy::init_type(); - Base::Interpreter().addType(Base::Vector2dPy::type_object(), + Base::InterpreterSingleton::addType(Base::Vector2dPy::type_object(), pBaseModule,"Vector2d"); // clang-format on } From 66e1710956819002d2e26d089d953d6a5f42c633 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 3 Mar 2025 16:03:54 -0600 Subject: [PATCH 19/31] App: Minor formatting from review Co-authored-by: Benjamin Nauck --- src/App/Application.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 980f719c24..03aba22b92 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -678,7 +678,7 @@ Document* Application::openDocument(const char * FileName, DocumentCreateFlags c } Document *Application::getDocumentByPath(const char *path, PathMatchMode checkCanonical) const { - if(!path || !path[0]) + if(!path || path[0] == '\0') return nullptr; if(DocFileMap.empty()) { for(const auto &v : DocMap) { @@ -2512,7 +2512,7 @@ void processProgramOptions(const variables_map& vm, std::map(); + auto configKey = vm["get-config"].as(); std::stringstream str; std::map::iterator pos; pos = mConfig.find(configKey); @@ -2695,8 +2695,8 @@ void Application::initConfig(int argc, char ** argv) if (SafeMode::SafeModeEnabled()) { Base::Console().Message("FreeCAD is running in _SAFE_MODE_.\n" - "Safe mode temporarily disables your configurations and " - "addons. Restart the application to exit safe mode.\n\n"); + "Safe mode temporarily disables your configurations and " + "addons. Restart the application to exit safe mode.\n\n"); } } LoadParameters(); @@ -3020,8 +3020,8 @@ void Application::LoadParameters() if (!Py_IsInitialized()) { Base::Console().Warning(" Parameter does not exist, writing initial one\n"); Base::Console().Message(" This warning normally means that FreeCAD is running for the first time\n" - " or the configuration was deleted or moved. FreeCAD is generating the standard\n" - " configuration.\n"); + " or the configuration was deleted or moved. FreeCAD is generating the standard\n" + " configuration.\n"); } } } @@ -3054,8 +3054,8 @@ void Application::LoadParameters() if (!Py_IsInitialized()) { Base::Console().Warning(" User settings do not exist, writing initial one\n"); Base::Console().Message(" This warning normally means that FreeCAD is running for the first time\n" - " or your configuration was deleted or moved. The system defaults\n" - " will be automatically generated for you.\n"); + " or your configuration was deleted or moved. The system defaults\n" + " will be automatically generated for you.\n"); } } } @@ -3181,7 +3181,7 @@ void getOldDataLocation(std::map& mConfig, std::vector< QString findUserHomePath(const QString& userHome) { return userHome.isEmpty() ? getUserHome() : userHome; - } +} /*! * \brief findPath @@ -3482,8 +3482,8 @@ std::string Application::FindHomePath(const char* call) if (!Py_IsInitialized()) { uint32_t sz = 0; - _NSGetExecutablePath( - nullptr, &sz); // function only returns "sz" if first arg is to small to hold value + // function only returns "sz" if first arg is to small to hold value + _NSGetExecutablePath(nullptr, &sz); if (const auto buf = new char[++sz]; _NSGetExecutablePath(buf, &sz) == 0) { char resolved[PATH_MAX]; From b090e27dcf2130655866433cd824fa179aa33d31 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 3 Mar 2025 19:24:15 -0600 Subject: [PATCH 20/31] App: Switch to isNullOrEmpty() --- src/App/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/App/Application.cpp b/src/App/Application.cpp index 03aba22b92..c149e20df4 100644 --- a/src/App/Application.cpp +++ b/src/App/Application.cpp @@ -678,7 +678,7 @@ Document* Application::openDocument(const char * FileName, DocumentCreateFlags c } Document *Application::getDocumentByPath(const char *path, PathMatchMode checkCanonical) const { - if(!path || path[0] == '\0') + if(Base::Tools::isNullOrEmpty(path)) return nullptr; if(DocFileMap.empty()) { for(const auto &v : DocMap) { From 01092f66b1e2f14aba96b7e7372d53cd74750f40 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 3 Mar 2025 22:43:36 -0600 Subject: [PATCH 21/31] TD: Add missing Boost headers to PCH --- src/Mod/TechDraw/App/PreCompiled.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Mod/TechDraw/App/PreCompiled.h b/src/Mod/TechDraw/App/PreCompiled.h index ebf3abca7b..da9dad16b5 100644 --- a/src/Mod/TechDraw/App/PreCompiled.h +++ b/src/Mod/TechDraw/App/PreCompiled.h @@ -48,6 +48,10 @@ #include #include #include +#include +#include +#include +#include #include // Qt From 9eca869c95eee9d20867b3d2353f4b1055928f4e Mon Sep 17 00:00:00 2001 From: Roy-043 Date: Tue, 4 Mar 2025 08:58:28 +0100 Subject: [PATCH 22/31] Draft: fix Trimex visibility change Fixes #19966 --- src/Mod/Draft/draftguitools/gui_trimex.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Mod/Draft/draftguitools/gui_trimex.py b/src/Mod/Draft/draftguitools/gui_trimex.py index f6a98143d4..4cfab71cac 100644 --- a/src/Mod/Draft/draftguitools/gui_trimex.py +++ b/src/Mod/Draft/draftguitools/gui_trimex.py @@ -158,7 +158,6 @@ class Trimex(gui_base_original.Modifier): self.finish() _err(translate("draft", "Trimex is not supported yet on this type of object.")) return - # self.obj.ViewObject.Visibility = False self.obj.ViewObject.LineColor = (0.5, 0.5, 0.5) self.obj.ViewObject.LineWidth = 1 self.extrudeMode = False @@ -393,7 +392,7 @@ class Trimex(gui_base_original.Modifier): newedges.append(_sh) ghost.on() - # resetting the visible edges + # resetting the edges if not reverse: li = list(range(npoint + 1, len(self.edges))) else: @@ -584,7 +583,6 @@ class Trimex(gui_base_original.Modifier): for g in self.ghost: g.finalize() if self.obj: - self.obj.ViewObject.Visibility = True if self.color: self.obj.ViewObject.LineColor = self.color if self.width: From 11255e83ad83f80ded4bc054342595a57fbc79d4 Mon Sep 17 00:00:00 2001 From: mosfet80 Date: Tue, 4 Mar 2025 01:21:58 +0100 Subject: [PATCH 23/31] Update actions/stale https://github.com/actions/stale/releases/tag/v9.1.0 --- .../auto-close_stale_issues_and_pull-requests.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-close_stale_issues_and_pull-requests.yml b/.github/workflows/auto-close_stale_issues_and_pull-requests.yml index ffcafbbd90..9433dbe202 100644 --- a/.github/workflows/auto-close_stale_issues_and_pull-requests.yml +++ b/.github/workflows/auto-close_stale_issues_and_pull-requests.yml @@ -22,7 +22,7 @@ jobs: steps: - name: '🧹 Tag & close stale unconfirmed bugs' id: stale_issues - uses: actions/stale@v9.0.0 + uses: actions/stale@v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: -1 @@ -49,7 +49,7 @@ jobs: - name: '🧹 Close stale requested feedback issues' id: awaiting_issues - uses: actions/stale@v9.0.0 + uses: actions/stale@v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: -1 @@ -77,7 +77,7 @@ jobs: - name: '🧹 Tag & close inactive issues' id: inactive_issues - uses: actions/stale@v9.0.0 + uses: actions/stale@v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: -1 @@ -108,7 +108,7 @@ jobs: - name: '🧹 Tag & close inactive PRs' id: inactive_pr - uses: actions/stale@v9.0.0 + uses: actions/stale@v9.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: -1 From fc35a58346aee9941424150bf25e7657218ef517 Mon Sep 17 00:00:00 2001 From: Furgo <148809153+furgo16@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:37:22 +0100 Subject: [PATCH 24/31] BIM: make ifcopenshell version upgrade more robust (#19823) * BIM: make ifcopenshell version detection and comparison more robust * Add reference to FreeCAD bug * Use create_pip_call for compatibility with different platforms/packages * Demote workaround logging category for quieter output * Correct ifcopenshell's actual version in the workaround as well * Allow upgrading packages * Provide more verbose output if there is an error with the pip call * Really print the exception message * Fix catch-all exception syntax * Remove ifcopenshell version workaround Weekly build 40444 containing the fix (https://github.com/FreeCAD/FreeCAD/pull/19861) has been released at https://github.com/FreeCAD/FreeCAD-Bundle/releases/tag/weekly-builds --- src/Mod/BIM/nativeifc/ifc_openshell.py | 62 +++++++++----------------- 1 file changed, 21 insertions(+), 41 deletions(-) diff --git a/src/Mod/BIM/nativeifc/ifc_openshell.py b/src/Mod/BIM/nativeifc/ifc_openshell.py index c0553efb7d..f38b866ee4 100644 --- a/src/Mod/BIM/nativeifc/ifc_openshell.py +++ b/src/Mod/BIM/nativeifc/ifc_openshell.py @@ -26,6 +26,8 @@ import FreeCAD import FreeCADGui +from packaging.version import Version +from addonmanager_utilities import create_pip_call translate = FreeCAD.Qt.translate QT_TRANSLATE_NOOP = FreeCAD.Qt.QT_TRANSLATE_NOOP @@ -49,8 +51,7 @@ class IFC_UpdateIOS: avail = self.get_avail_version() if avail: if version: - comp = self.compare_versions(avail, version) - if comp > 0: + if Version(version) < Version(avail): self.show_dialog("update", avail) else: self.show_dialog("uptodate") @@ -92,6 +93,7 @@ class IFC_UpdateIOS: if mode in ["update", "install"]: result = self.install() if result: + FreeCAD.Console.PrintLog(f"{result.stdout}\n") text = translate("BIM", "IfcOpenShell update successfully installed.") buttons = QtGui.QMessageBox.Ok reply = QtGui.QMessageBox.information(None, title, text, buttons) @@ -104,7 +106,7 @@ class IFC_UpdateIOS: from PySide import QtCore, QtGui QtGui.QApplication.setOverrideCursor(QtCore.Qt.WaitCursor) vendor_path = utils.get_pip_target_directory() - args = ["install", "--disable-pip-version-check", "--target", vendor_path, "ifcopenshell"] + args = ["install", "--upgrade", "--disable-pip-version-check", "--target", vendor_path, "ifcopenshell"] result = self.run_pip(args) QtGui.QApplication.restoreOverrideCursor() return result @@ -115,14 +117,17 @@ class IFC_UpdateIOS: import addonmanager_utilities as utils import freecad.utils - cmd = [freecad.utils.get_python_exe(), "-m", "pip"] - cmd.extend(args) + from subprocess import CalledProcessError + + cmd = create_pip_call(args) result = None try: result = utils.run_interruptable_subprocess(cmd) - except: + except CalledProcessError as pe: + FreeCAD.Console.PrintError(pe.stderr) + except Exception as e: text = translate("BIM","Unable to run pip. Please ensure pip is installed on your system.") - FreeCAD.Console.PrintError(text + "\n") + FreeCAD.Console.PrintError(f"{text} {str(e)}\n") return result @@ -130,19 +135,19 @@ class IFC_UpdateIOS: """Retrieves the current ifcopenshell version""" import addonmanager_utilities as utils + from packaging.version import InvalidVersion + try: import ifcopenshell - version = ifcopenshell.version + version = ifcopenshell.version + try: + Version(version) + except InvalidVersion: + FreeCAD.Console.PrintWarning(f"Invalid IfcOpenShell version: {version}\n") + version = "" except: version = "" - if version.startswith("v"): - # this is a pip version - vendor_path = utils.get_pip_target_directory() - result = self.run_pip(["list", "--path", vendor_path]) - if result: - result = result.stdout.split() - if "ifcopenshell" in result: - version = result[result.index("ifcopenshell")+1] + return version @@ -159,31 +164,6 @@ class IFC_UpdateIOS: return None - def compare_versions(self, v1, v2): - """Compare two version strings in the form '0.7.0' or v0.7.0""" - - # code from https://www.geeksforgeeks.org/compare-two-version-numbers - - arr1 = v1.replace("v","").split(".") - arr2 = v2.replace("v","").split(".") - n = len(arr1) - m = len(arr2) - arr1 = [int(i) for i in arr1] - arr2 = [int(i) for i in arr2] - if n > m: - for i in range(m, n): - arr2.append(0) - elif m > n: - for i in range(n, m): - arr1.append(0) - for i in range(len(arr1)): - if arr1[i] > arr2[i]: - return 1 - elif arr2[i] > arr1[i]: - return -1 - return 0 - - FreeCADGui.addCommand("IFC_UpdateIOS", IFC_UpdateIOS()) From 3c77bd4fcfa74c02fa65d35043e24cc457ab46ac Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 3 Mar 2025 15:01:37 -0600 Subject: [PATCH 25/31] Addon Manager: Adjust use of QDialogButtonBox --- src/Mod/AddonManager/update_all.ui | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/Mod/AddonManager/update_all.ui b/src/Mod/AddonManager/update_all.ui index 266b779f9a..27672917d5 100644 --- a/src/Mod/AddonManager/update_all.ui +++ b/src/Mod/AddonManager/update_all.ui @@ -26,8 +26,14 @@ + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + - QAbstractItemView::EditTrigger::NoEditTriggers + QAbstractItemView::NoEditTriggers false @@ -39,10 +45,10 @@ false - QAbstractItemView::SelectionMode::NoSelection + QAbstractItemView::NoSelection - QAbstractItemView::SelectionBehavior::SelectRows + QAbstractItemView::SelectItems false @@ -60,11 +66,8 @@ - - Qt::Orientation::Horizontal - - QDialogButtonBox::StandardButton::Cancel + QDialogButtonBox::Cancel From 1ab8bed308118badce894f4e29f237ae3fbe8254 Mon Sep 17 00:00:00 2001 From: mosfet80 Date: Tue, 4 Mar 2025 23:10:12 +0100 Subject: [PATCH 26/31] clean FindOCC.cmake (#19755) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * clean FindOCC.cmake cmake version is always major then 3.16.3 . * Update cMake/FindOCC.cmake Co-authored-by: Benjamin Bræstrup Sayoc * Update cMake/FindOCC.cmake Co-authored-by: Benjamin Bræstrup Sayoc --------- Co-authored-by: Benjamin Bræstrup Sayoc --- cMake/FindOCC.cmake | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/cMake/FindOCC.cmake b/cMake/FindOCC.cmake index 21e9465282..552f0b1896 100644 --- a/cMake/FindOCC.cmake +++ b/cMake/FindOCC.cmake @@ -10,12 +10,10 @@ # we first try to find opencascade directly: if (NOT OCCT_CMAKE_FALLBACK) find_package(OpenCASCADE CONFIG QUIET) - if (NOT (CMAKE_VERSION VERSION_LESS 3.6.0)) - get_property(flags DIRECTORY PROPERTY COMPILE_DEFINITIONS) - # OCCT 7.5 adds this define that causes hundreds of compiler warnings with Qt5.x, so remove it again - list(FILTER flags EXCLUDE REGEX [[GL_GLEXT_LEGACY]]) - set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS ${flags}) - endif () + get_property(flags DIRECTORY PROPERTY COMPILE_DEFINITIONS) + # OCCT 7.5 adds this define that causes hundreds of compiler warnings with Qt5.x, so remove it again + list(FILTER flags EXCLUDE REGEX [[GL_GLEXT_LEGACY]]) + set_property(DIRECTORY PROPERTY COMPILE_DEFINITIONS ${flags}) endif () if (OpenCASCADE_FOUND) set(OCC_FOUND ${OpenCASCADE_FOUND}) From f8032e9ddbb3b528c573a7098c9393516a73930b Mon Sep 17 00:00:00 2001 From: Jacob Oursland Date: Tue, 4 Mar 2025 09:49:37 -0700 Subject: [PATCH 27/31] About: Add build date to clipboard data. --- src/Gui/Dialogs/DlgAbout.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Gui/Dialogs/DlgAbout.cpp b/src/Gui/Dialogs/DlgAbout.cpp index 45b253a232..357c5c39c6 100644 --- a/src/Gui/Dialogs/DlgAbout.cpp +++ b/src/Gui/Dialogs/DlgAbout.cpp @@ -580,6 +580,7 @@ void AboutDialog::copyToClipboard() QString point = QString::fromStdString(config["BuildVersionPoint"]); QString suffix = QString::fromStdString(config["BuildVersionSuffix"]); QString build = QString::fromStdString(config["BuildRevision"]); + QString disda = QString::fromStdString(config["BuildRevisionDate"]); QString deskEnv = QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP"), @@ -628,6 +629,7 @@ void AboutDialog::copyToClipboard() str << " Snap " << snap; } str << '\n'; + str << "Build date: " << disda << "\n"; #if defined(_DEBUG) || defined(DEBUG) str << "Build type: Debug\n"; From f3c6ea92d63dd88d2a57ecf7a54bf2fd8d4d767d Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 4 Mar 2025 16:14:16 -0600 Subject: [PATCH 28/31] Apply suggestions from code review Co-authored-by: Benjamin Nauck --- src/Gui/Dialogs/DlgAbout.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Gui/Dialogs/DlgAbout.cpp b/src/Gui/Dialogs/DlgAbout.cpp index 357c5c39c6..ca6c8b9011 100644 --- a/src/Gui/Dialogs/DlgAbout.cpp +++ b/src/Gui/Dialogs/DlgAbout.cpp @@ -580,7 +580,7 @@ void AboutDialog::copyToClipboard() QString point = QString::fromStdString(config["BuildVersionPoint"]); QString suffix = QString::fromStdString(config["BuildVersionSuffix"]); QString build = QString::fromStdString(config["BuildRevision"]); - QString disda = QString::fromStdString(config["BuildRevisionDate"]); + QString buildDate = QString::fromStdString(config["BuildRevisionDate"]); QString deskEnv = QProcessEnvironment::systemEnvironment().value(QStringLiteral("XDG_CURRENT_DESKTOP"), @@ -629,7 +629,7 @@ void AboutDialog::copyToClipboard() str << " Snap " << snap; } str << '\n'; - str << "Build date: " << disda << "\n"; + str << "Build date: " << buildDate << "\n"; #if defined(_DEBUG) || defined(DEBUG) str << "Build type: Debug\n"; From 56a87cbf555df60a6b026e5d975e30580533e948 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Tue, 4 Mar 2025 18:28:07 -0600 Subject: [PATCH 29/31] CI: Add merge_group as trigger Required to force CI run on merge queue --- .github/workflows/CI_master.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/CI_master.yml b/.github/workflows/CI_master.yml index ca74ea454c..55b2db64d7 100644 --- a/.github/workflows/CI_master.yml +++ b/.github/workflows/CI_master.yml @@ -26,7 +26,7 @@ name: FreeCAD master CI -on: [workflow_dispatch, push, pull_request] +on: [workflow_dispatch, push, pull_request, merge_group] concurrency: group: FC-CI-${{ github.head_ref || github.run_id }} From 46b2ec526b434b575ef4c56bff17eba8959f9b35 Mon Sep 17 00:00:00 2001 From: Syres916 <46537884+Syres916@users.noreply.github.com> Date: Thu, 23 Jan 2025 23:56:14 +0000 Subject: [PATCH 30/31] [Gui] Trigger the same checks for Drag & Drop files as File > Open --- src/Gui/Application.cpp | 30 ++++++++++++++++++++++++++++++ src/Gui/Application.h | 4 ++++ src/Gui/CommandDoc.cpp | 24 ++---------------------- 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index 5a13553bf4..c8827f4b01 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -707,6 +708,11 @@ void Application::importFrom(const char* FileName, const char* DocName, const ch // load the file with the module if (File.hasExtension("FCStd")) { Command::doCommand(Command::App, "%s.open(u\"%s\")", Module, unicodepath.c_str()); + setStatus(UserInitiatedOpenDocument, false); + App::Document* doc = App::GetApplication().getActiveDocument(); + checkPartialRestore(doc); + checkRestoreError(doc); + checkForRecomputes(); if (activeDocument()) { activeDocument()->setModified(false); } @@ -1007,6 +1013,30 @@ void Application::checkForRecomputes() { "Please check report view for more details.")); } +void Application::checkPartialRestore(App::Document* doc) +{ + if (doc && doc->testStatus(App::Document::PartialRestore)) { + QMessageBox::critical( + getMainWindow(), + QObject::tr("Error"), + QObject::tr("There were errors while loading the file. Some data might have been " + "modified or not recovered at all. Look in the report view for more " + "specific information about the objects involved.")); + } +} + +void Application::checkRestoreError(App::Document* doc) +{ + if (doc && doc->testStatus(App::Document::RestoreError)) { + QMessageBox::critical( + getMainWindow(), + QObject::tr("Error"), + QObject::tr("There were serious errors while loading the file. Some data might have " + "been modified or not recovered at all. Saving the project will most " + "likely result in loss of data.")); + } +} + void Application::slotActiveDocument(const App::Document& Doc) { std::map::iterator doc = d->documents.find(&Doc); diff --git a/src/Gui/Application.h b/src/Gui/Application.h index e358e6008c..54f7c4dd15 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -79,6 +79,10 @@ public: App::Document *reopen(App::Document *doc); /// Prompt about recomputing if needed static void checkForRecomputes(); + /// Prompt about PartialRestore + void checkPartialRestore(App::Document* doc); + /// Prompt for Errors on open + void checkRestoreError(App::Document* doc); //@} diff --git a/src/Gui/CommandDoc.cpp b/src/Gui/CommandDoc.cpp index 6d9274b69a..8ed8880d7e 100644 --- a/src/Gui/CommandDoc.cpp +++ b/src/Gui/CommandDoc.cpp @@ -96,26 +96,6 @@ StdCmdOpen::StdCmdOpen() void StdCmdOpen::activated(int iMsg) { - // clang-format off - auto checkPartialRestore = [](App::Document* doc) { - if (doc && doc->testStatus(App::Document::PartialRestore)) { - QMessageBox::critical(getMainWindow(), QObject::tr("Error"), - QObject::tr("There were errors while loading the file. Some data might have been " - "modified or not recovered at all. Look in the report view for more " - "specific information about the objects involved.")); - } - }; - - auto checkRestoreError = [](App::Document* doc) { - if (doc && doc->testStatus(App::Document::RestoreError)) { - QMessageBox::critical(getMainWindow(), QObject::tr("Error"), - QObject::tr("There were serious errors while loading the file. Some data might have " - "been modified or not recovered at all. Saving the project will most " - "likely result in loss of data.")); - } - }; - // clang-format on - Q_UNUSED(iMsg); // fill the list of registered endings @@ -183,8 +163,8 @@ void StdCmdOpen::activated(int iMsg) App::Document *doc = App::GetApplication().getActiveDocument(); - checkPartialRestore(doc); - checkRestoreError(doc); + getGuiApplication()->checkPartialRestore(doc); + getGuiApplication()->checkRestoreError(doc); } } } From 27dd14174e991c4682b116caa818d0b67b0673fa Mon Sep 17 00:00:00 2001 From: Andrew Shkolik Date: Wed, 26 Feb 2025 20:51:23 -0600 Subject: [PATCH 31/31] fix #18356 Sketcher: External reference line defaults to geometry, but vertex does not --- src/Mod/Sketcher/App/SketchObject.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/Mod/Sketcher/App/SketchObject.cpp b/src/Mod/Sketcher/App/SketchObject.cpp index 0bfa228d53..4c9b8be7fe 100644 --- a/src/Mod/Sketcher/App/SketchObject.cpp +++ b/src/Mod/Sketcher/App/SketchObject.cpp @@ -366,10 +366,23 @@ void SketchObject::buildShape() { auto egf = ExternalGeometryFacade::getFacade(geo); if(!egf->testFlag(ExternalGeometryExtension::Defining)) continue; + auto indexedName = Data::IndexedName::fromConst("ExternalEdge", i-1); - shapes.push_back(getEdge(geo, convertSubName(indexedName, false).c_str())); - if (checkSmallEdge(shapes.back())) { - FC_WARN("Edge too small: " << indexedName); + + if (geo->isDerivedFrom()) { + Part::TopoShape vertex(TopoDS::Vertex(geo->toShape())); + if (!vertex.hasElementMap()) { + vertex.resetElementMap(std::make_shared()); + } + vertex.setElementName(Data::IndexedName::fromConst("Vertex", 1), + Data::MappedName::fromRawData(convertSubName(indexedName, false).c_str()),0L); + vertices.push_back(vertex); + vertices.back().copyElementMap(vertex, Part::OpCodes::Sketch); + } else { + shapes.push_back(getEdge(geo, convertSubName(indexedName, false).c_str())); + if (checkSmallEdge(shapes.back())) { + FC_WARN("Edge too small: " << indexedName); + } } }