Merge branch 'main' into patch-1
This commit is contained in:
2
.github/workflows/CI_master.yml
vendored
2
.github/workflows/CI_master.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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
|
||||
|
||||
107
.github/workflows/sub_lint.yml
vendored
107
.github/workflows/sub_lint.yml
vendored
@@ -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 }}
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
//@}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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())
|
||||
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -46,6 +46,7 @@ public:
|
||||
|
||||
private:
|
||||
QDir _customFolderDirectory;
|
||||
bool _showOnlyFCStd; // Show only FreeCAD files
|
||||
};
|
||||
|
||||
} // namespace Start
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
159
tools/lint/generic_checks.py
Executable 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
95
tools/lint/utils.py
Normal 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)
|
||||
Reference in New Issue
Block a user