Merge branch 'main' into patch-1

This commit is contained in:
clsergent
2025-03-05 07:46:56 +01:00
committed by GitHub
22 changed files with 576 additions and 361 deletions

View File

@@ -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 }}

View File

@@ -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

View File

@@ -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 "<details><summary>:information_source: Found $lineendings problems with line endings</summary>" >> ${{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 "</details>" >> ${{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 "<details><summary>:information_source: Found $whitespaceErrors trailing whitespace</summary>" >> ${{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 "</details>" >> ${{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 "<details><summary>:information_source: Found $tabErrors tabs, better to use spaces</summary>" >> ${{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 "</details>" >> ${{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 }}

View File

@@ -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})

View File

@@ -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)

View File

@@ -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,81 +377,49 @@ 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
}
// 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
@@ -551,7 +519,7 @@ Document* Application::newDocument(const char * proposedName, const char * propo
bool Application::closeDocument(const char* name)
{
map<string,Document*>::iterator pos = DocMap.find( name );
auto pos = DocMap.find( name );
if (pos == DocMap.end()) // no such document
return false;
@@ -586,9 +554,8 @@ void Application::closeAllDocuments()
App::Document* Application::getDocument(const char *Name) const
{
std::map<std::string,Document*>::const_iterator pos;
pos = DocMap.find(Name);
const auto pos = DocMap.find(Name);
if (pos == DocMap.end())
return nullptr;
@@ -610,6 +577,7 @@ const char * Application::getDocumentName(const App::Document* doc) const
std::vector<App::Document*> Application::getDocuments() const
{
std::vector<App::Document*> docs;
docs.reserve(DocMap.size());
for (const auto & it : DocMap)
docs.push_back(it.second);
return docs;
@@ -710,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(Base::Tools::isNullOrEmpty(path))
return nullptr;
if(DocFileMap.empty()) {
for(const auto &v : DocMap) {
@@ -988,8 +956,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;
}
@@ -1004,10 +972,13 @@ Document* Application::openDocumentPrivate(const char * FileName,
}
}
if(!isMainDoc)
if (!isMainDoc) {
return nullptr;
else if(doc)
}
if (doc) {
return doc;
}
}
std::string name;
@@ -1088,10 +1059,7 @@ void Application::setActiveDocument(const char* Name)
return;
}
std::map<std::string,Document*>::iterator pos;
pos = DocMap.find(Name);
if (pos != DocMap.end()) {
if (const auto pos = DocMap.find(Name); pos != DocMap.end()) {
setActiveDocument(pos->second);
}
else {
@@ -1196,7 +1164,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())
@@ -1211,7 +1179,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;
@@ -1225,7 +1193,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())
@@ -1246,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:
@@ -1269,7 +1237,7 @@ std::set<DocumentObject *> 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<int>(links.size())>=maxCount)
break;
}
} else {
@@ -1277,7 +1245,7 @@ std::set<DocumentObject *> 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<int>(links.size())>=maxCount)
break;
}
}
@@ -1302,10 +1270,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<std::string,Base::Reference<ParameterManager>> &
@@ -1346,7 +1312,7 @@ Base::Reference<ParameterGrp> 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");
@@ -1413,8 +1379,10 @@ std::vector<std::string> Application::getImportModules(const char* Type) const
std::vector<std::string> Application::getImportModules() const
{
std::vector<std::string> 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;
@@ -1536,8 +1504,10 @@ std::vector<std::string> Application::getExportModules(const char* Type) const
std::vector<std::string> Application::getExportModules() const
{
std::vector<std::string> 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;
@@ -1853,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
@@ -1886,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
@@ -1906,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!");
@@ -2370,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;
str << "Usage: " << exe << " [options] File1 File2 ..." << endl << endl;
str << visible << endl;
str << exe << '\n' << '\n';
str << "For a detailed description see https://www.freecad.org/wiki/Start_up_and_Configuration" << '\n'<<'\n';
str << "Usage: " << exe << " [options] File1 File2 ..." << '\n' << '\n';
str << visible << '\n';
throw Base::ProgramInformation(str.str());
}
@@ -2395,7 +2365,7 @@ void parseProgramOptions(int ac, char ** av, const string& exe, variables_map& v
Base::Console().Error("Could no open the response file\n");
std::stringstream str;
str << "Could no open the response file: '"
<< vm["response-file"].as<string>() << "'" << endl;
<< vm["response-file"].as<string>() << "'" << '\n';
throw Base::UnknownProgramOption(str.str());
}
// Read the whole file into a string
@@ -2404,10 +2374,10 @@ void parseProgramOptions(int ac, char ** av, const string& exe, variables_map& v
// Split the file content
char_separator<char> sep(" \n\r");
tokenizer<char_separator<char> > tok(ss.str(), sep);
vector<string> args;
copy(tok.begin(), tok.end(), back_inserter(args));
vector<string> 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);
}
}
@@ -2417,7 +2387,7 @@ void processProgramOptions(const variables_map& vm, std::map<std::string,std::st
if (vm.count("version")) {
std::stringstream str;
str << mConfig["ExeName"] << " " << mConfig["ExeVersion"]
<< " Revision: " << mConfig["BuildRevision"] << std::endl;
<< " Revision: " << mConfig["BuildRevision"] << '\n';
if (vm.count("verbose")) {
str << "\nLibrary versions:\n";
str << "boost " << BOOST_LIB_VERSION << '\n';
@@ -2440,7 +2410,7 @@ void processProgramOptions(const variables_map& vm, std::map<std::string,std::st
}
if (vm.count("module-path")) {
vector<string> Mods = vm["module-path"].as< vector<string> >();
auto Mods = vm["module-path"].as< vector<string> >();
string temp;
for (const auto & It : Mods)
temp += It + ";";
@@ -2458,7 +2428,7 @@ void processProgramOptions(const variables_map& vm, std::map<std::string,std::st
}
if (vm.count("python-path")) {
vector<string> Paths = vm["python-path"].as< vector<string> >();
auto Paths = vm["python-path"].as< vector<string> >();
for (const auto & It : Paths)
Base::Interpreter().addPythonPath(It.c_str());
}
@@ -2474,7 +2444,7 @@ void processProgramOptions(const variables_map& vm, std::map<std::string,std::st
}
if (vm.count("input-file")) {
vector<string> files(vm["input-file"].as< vector<string> >());
auto files(vm["input-file"].as< vector<string> >());
int OpenFileCount=0;
for (const auto & It : files) {
@@ -2536,25 +2506,25 @@ void processProgramOptions(const variables_map& vm, std::map<std::string,std::st
if (vm.count("dump-config")) {
std::stringstream str;
for (const auto & it : mConfig) {
str << it.first << "=" << it.second << std::endl;
str << it.first << "=" << it.second << '\n';
}
throw Base::ProgramInformation(str.str());
}
if (vm.count("get-config")) {
std::string configKey = vm["get-config"].as<string>();
auto configKey = vm["get-config"].as<string>();
std::stringstream str;
std::map<std::string,std::string>::iterator pos;
pos = mConfig.find(configKey);
if (pos != mConfig.end()) {
str << pos->second;
}
str << std::endl;
str << '\n';
throw Base::ProgramInformation(str.str());
}
if (vm.count("set-config")) {
std::vector<std::string> configKeyValue = vm["set-config"].as< std::vector<std::string> >();
auto configKeyValue = vm["set-config"].as< std::vector<std::string> >();
for (const auto& it : configKeyValue) {
auto pos = it.find('=');
if (pos != std::string::npos) {
@@ -2653,9 +2623,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();
@@ -2700,10 +2670,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(),
@@ -2725,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();
@@ -2747,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<int>(v.second));
}
#endif
}
else {
*Base::Console().GetLogLevel(v.first.c_str()) = v.second;
*Base::Console().GetLogLevel(v.first.c_str()) = static_cast<int>(v.second);
}
}
@@ -2829,18 +2799,18 @@ 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);
// 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<UnitSystem>(hGrp->GetInt("UserSchema", 0)));
UnitsApi::setDecimals(static_cast<int>(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<int>(hGrp->GetInt("FracInch", Base::QuantityFormat::getDefaultDenominator()));
Base::QuantityFormat::setDefaultDenominator(denom);
@@ -2965,8 +2935,8 @@ void Application::processCmdLineFiles()
}
}
const std::map<std::string,std::string>& cfg = Application::Config();
std::map<std::string,std::string>::const_iterator it = cfg.find("SaveFile");
const std::map<std::string, std::string>& cfg = Application::Config();
auto it = cfg.find("SaveFile");
if (it != cfg.end()) {
std::string output = it->second;
output = Base::Tools::escapeEncodeFilename(output);
@@ -3045,13 +3015,13 @@ 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");
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");
}
}
}
@@ -3064,10 +3034,10 @@ 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<std::string, std::string>::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()) {
@@ -3084,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");
}
}
}
@@ -3210,12 +3180,7 @@ void getOldDataLocation(std::map<std::string,std::string>& mConfig, std::vector<
*/
QString findUserHomePath(const QString& userHome)
{
if (userHome.isEmpty()) {
return getUserHome();
}
else {
return userHome;
}
return userHome.isEmpty() ? getUserHome() : userHome;
}
/*!
@@ -3516,12 +3481,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];
// function only returns "sz" if first arg is to small to hold value
_NSGetExecutablePath(nullptr, &sz);
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;

View File

@@ -26,6 +26,7 @@
#include <boost/interprocess/sync/file_lock.hpp>
#include <Inventor/errors/SoDebugError.h>
#include <Inventor/errors/SoError.h>
#include <QCheckBox>
#include <QCloseEvent>
#include <QDir>
#include <QFileInfo>
@@ -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<const App::Document*, Gui::Document*>::iterator doc = d->documents.find(&Doc);

View File

@@ -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);
//@}

View File

@@ -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);
}
}
}

View File

@@ -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 buildDate = 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: " << buildDate << "\n";
#if defined(_DEBUG) || defined(DEBUG)
str << "Build type: Debug\n";

View File

@@ -26,8 +26,14 @@
</item>
<item>
<widget class="QTableWidget" name="tableWidget">
<property name="horizontalScrollBarPolicy">
<enum>Qt::ScrollBarAlwaysOff</enum>
</property>
<property name="sizeAdjustPolicy">
<enum>QAbstractScrollArea::AdjustToContents</enum>
</property>
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="tabKeyNavigation">
<bool>false</bool>
@@ -39,10 +45,10 @@
<bool>false</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::SelectionMode::NoSelection</enum>
<enum>QAbstractItemView::NoSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
<enum>QAbstractItemView::SelectItems</enum>
</property>
<property name="showGrid">
<bool>false</bool>
@@ -60,11 +66,8 @@
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::StandardButton::Cancel</set>
<set>QDialogButtonBox::Cancel</set>
</property>
</widget>
</item>

View File

@@ -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())

View File

@@ -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:

View File

@@ -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::GeomPoint>()) {
Part::TopoShape vertex(TopoDS::Vertex(geo->toShape()));
if (!vertex.hasElementMap()) {
vertex.resetElementMap(std::make_shared<Data::ElementMap>());
}
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);
}
}
}

View File

@@ -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) {

View File

@@ -46,6 +46,7 @@ public:
private:
QDir _customFolderDirectory;
bool _showOnlyFCStd; // Show only FreeCAD files
};
} // namespace Start

View File

@@ -20,6 +20,20 @@
<string>Contents</string>
</property>
<layout class="QGridLayout" name="gridLayout_2">
<item row="2" column="0">
<widget class="QLabel" name="labelShowOnlyFCStd">
<property name="text">
<string>Show only FreeCAD files in additional folder</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Show examples folder contents</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="labelCustomFolder">
<property name="text">
@@ -27,6 +41,22 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::PrefFileChooser" name="fileChooserCustomFolder" native="true">
<property name="toolTip">
<string>An optional custom folder to be displayed on the Start page.</string>
</property>
<property name="mode">
<enum>Gui::FileChooser::Directory</enum>
</property>
<property name="prefEntry" stdset="0">
<cstring>CustomFolder</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Start</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="Gui::PrefCheckBox" name="checkBoxShowExamples">
<property name="toolTip">
@@ -49,29 +79,25 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="Gui::PrefFileChooser" name="fileChooserCustomFolder" native="true">
<item row="2" column="1">
<widget class="Gui::PrefCheckBox" name="checkBoxShowOnlyFCStd">
<property name="toolTip">
<string>An optional custom folder to be displayed on the Start page.</string>
<string>If the additional folder contents should include only .FCStd files</string>
</property>
<property name="mode">
<enum>Gui::FileChooser::Directory</enum>
<property name="layoutDirection">
<enum>Qt::RightToLeft</enum>
</property>
<property name="text">
<string/>
</property>
<property name="prefEntry" stdset="0">
<cstring>CustomFolder</cstring>
<cstring>ShowOnlyFCStd</cstring>
</property>
<property name="prefPath" stdset="0">
<cstring>Mod/Start</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Show examples folder contents</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@@ -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();
}
/**

View File

@@ -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);

View File

@@ -48,6 +48,10 @@
#include <boost/graph/boyer_myrvold_planar_test.hpp>
#include <boost/graph/is_kuratowski_subgraph.hpp>
#include <boost/random.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost_regex.hpp>
// Qt

159
tools/lint/generic_checks.py Executable file
View File

@@ -0,0 +1,159 @@
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 = 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:
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 = [idx if "\t" in line for idx, line in enumerate(lines, start=1)]
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"<details><summary>:information_source: Found {count} issue(s) in {section_title}</summary>\n"
)
report.append("```")
for file, details in issues.items():
report.append(f"{file}: {details}")
report.append("```\n</details>\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()

95
tools/lint/utils.py Normal file
View File

@@ -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)