Merge remote-tracking branch 'upstream/HEAD'

This commit is contained in:
Thom de Jong
2025-11-30 12:43:31 +01:00
1248 changed files with 182006 additions and 174528 deletions

View File

@@ -0,0 +1,92 @@
name: Report a Problem
description: Have you found something that does not work well, is too hard to do or is missing altogether? Please create a Problem Report.
labels: ["Status: Needs triage","Status: Needs confirmation"]
body:
- type: markdown
attributes:
value: |
Thanks for taking the time to fill out this problem report! Please [search](https://github.com/FreeCAD/FreeCAD/issues) if a similar issue already exists and check out [how to report issues](https://github.com/FreeCAD/FreeCAD?tab=readme-ov-file#reporting-issues). By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/FreeCAD/FreeCAD/blob/main/CODE_OF_CONDUCT.md).
- type: dropdown
id: wb
attributes:
label: Workbench affected?
options:
- Assembly
- BIM
- CAM
- Core (App, Gui,...)
- Draft
- FEM
- Material
- Measurement
- Mesh
- Part
- Part Design
- Sketcher
- Spreadsheet
- TechDraw
- Other (specify in description)
- type: textarea
id: description
attributes:
label: Problem description
description: Describe the problem and how it impacts user experience, workflow, maintainability or performance. You can attach images or log files by clicking this area to highlight it and then dragging files in. To attach a FCStd file, ZIP it first.
placeholder: Describe your problem briefly.
validations:
required: true
- type: textarea
id: steps_to_reproduce
attributes:
label: Steps to reproduce
description: If the problem appears to be a bug with the current functionality, provide a test case or recipe that reliably reproduces the issue. Ideally [record a macro](https://wiki.freecad.org/Std_DlgMacroRecord) and attach it. Please also add an example file as ZIP.
placeholder: |
Drag an example file as ZIP into this textbox to attach it.
Steps to reproduce the behavior:
1. Open my example file
2. Go to '...'
3. Click on '...'
validations:
required: true
- type: textarea
id: expected_behavior
attributes:
label: Expected behavior
description: A clear and concise description of what you expected to happen when following the provided steps above.
placeholder: What is the expected behavior?
validations:
required: true
- type: textarea
id: actual_behavior
attributes:
label: Actual behavior
description: A clear and concise description of what actually happens. If applicable, add screenshots to help explain the problem.
placeholder: What is actually happening? You can add screenshorts or videos by dragging them into this textbox.
validations:
required: true
- type: textarea
id: dev_version
attributes:
label: Development version About Info (in Safe Mode)
description: Download the latest weekly [development release](https://github.com/FreeCAD/FreeCAD/releases) and try reproducing the issue in safe mode (Help → Restart in Safe Mode). Use the [About FreeCAD](https://wiki.freecad.org/About) dialog to copy your full version information and paste it here.
placeholder: |
1. Download the latest weekly development version (link above).
2. Make sure to run in Safe Mode (Help -> Restart in Safe Mode) to exclude interference from 3rd party addons.
3. If the issue is still reproducible, open the About FreeCAD dialog and copy the full version info here.
render: shell
validations:
required: true
- type: textarea
id: good_version
attributes:
label: Last known good version (optional)
description: If this is a regression, paste the [About FreeCAD](https://wiki.freecad.org/About) info from the last known working version.
placeholder: If the problem did not exist in a previous version, paste the About FreeCAD info from that version here.
render: shell

View File

@@ -1,73 +0,0 @@
name: Report a Problem
description: Have you found something that does not work well, is too hard to do or is missing altogether? Please create a Problem Report.
labels: ["Status: Needs triage","Status: Needs confirmation"]
body:
- type: checkboxes
id: existing_issue
attributes:
label: Is there an existing issue for this?
description: Please [search](https://github.com/FreeCAD/FreeCAD/issues) to see if a similar issue already exists for the problem you encountered.
options:
- label: I have searched the existing issues
required: true
- type: textarea
id: description
attributes:
label: Problem description
description: Describe the problem and how it impacts user experience, workflow, maintainability or speed of the code. If the problem appears to be a bug with the current functionality, provide as test case or recipe that reproduces the error. Ideally record a macro and attach it.
placeholder: Description of the problem
validations:
required: true
- type: textarea
id: full_version
attributes:
label: Full version info
description: Please use the About FreeCAD dialog to copy your full version information and paste it here. Try reproducing the issue by restarting FreeCAD in safe mode too.
render: shell
validations:
required: true
- type: dropdown
id: wb
attributes:
label: Subproject(s) affected?
options:
- Addon Manager
- Assembly
- BIM
- CAM
- Core
- Draft
- Expressions
- FEM
- File formats
- Material
- Measurement
- Mesh
- OpenSCAD
- Part
- PartDesign
- Project Tools & Websites
- Sketcher
- Spreadsheet
- TechDraw
- Other (specify in description)
- type: textarea
id: anything_else
attributes:
label: Anything else?
description: |
Links? References? Anything that will give us more context about the issue you are encountering!
If there is a discussion about the problem on the forum, provide link(s) here.
You can upload or copy your macro here to speed up the diagnosis and debugging.
Tip: You can attach images or log files by clicking this area to highlight it and then dragging files in. To attach a FCStd file, ZIP it first (GitHub won't recognize the extension otherwise).
validations:
required: false
- type: checkboxes
id: terms
attributes:
label: Code of Conduct
description: By submitting this issue, you agree to follow our [Code of Conduct](https://github.com/FreeCAD/FreeCAD/blob/main/CODE_OF_CONDUCT.md)
options:
- label: I agree to follow this project's Code of Conduct
required: true

View File

@@ -1,5 +1,7 @@
name: Weekly Build
name: Build Release
on:
release:
types: [created]
schedule:
- cron: "0 0 * * 3"
workflow_dispatch:
@@ -8,10 +10,10 @@ permissions:
contents: write
jobs:
tag_build:
upload_src:
runs-on: ubuntu-latest
outputs:
build_tag: ${{ steps.tag_build.outputs.build_tag }}
build_tag: ${{ steps.get_tag.outputs.build_tag }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2
@@ -21,20 +23,25 @@ jobs:
- name: Checkout Source
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.sha }}
fetch-depth: 2
fetch-tags: true
submodules: 'recursive'
- name: Tag Build
id: tag_build
- name: get tag and create release if weekly
id: get_tag
shell: bash -l {0}
env:
GH_TOKEN: ${{ github.token }}
run: |
export BUILD_TAG=weekly-$(date "+%Y.%m.%d")
if [ "${{ github.event_name }}" = "release" ]; then
export BUILD_TAG="${{ github.event.release.tag_name }}"
else
export BUILD_TAG=weekly-$(date "+%Y.%m.%d")
gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" -F .github/workflows/weekly-build-notes.md --prerelease || true
fi
echo "BUILD_TAG=${BUILD_TAG}" >> "$GITHUB_ENV"
echo "build_tag=${BUILD_TAG}" >> "$GITHUB_OUTPUT"
gh release create ${BUILD_TAG} --title "Development Build ${BUILD_TAG}" -F .github/workflows/weekly-build-notes.md --prerelease || true
- name: Upload Source
id: upload_source
@@ -42,10 +49,10 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
python3 package/rattler-build/scripts/make_version_file.py ../freecad_version.txt
python3 package/scripts/write_version_info.py ../freecad_version.txt
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
git config user.name 'github-actions[bot]'
git apply package/rattler-build/scripts/disable_git_info.patch
git apply package/disable_git_info.patch
git commit -a -m "Disable git info write to Version.h"
git archive HEAD -o freecad_source_${BUILD_TAG}.tar
git submodule foreach --recursive \
@@ -57,13 +64,13 @@ jobs:
gh release upload --clobber ${BUILD_TAG} "freecad_source_${BUILD_TAG}.tar.gz" "freecad_source_${BUILD_TAG}.tar.gz-SHA256.txt"
build:
needs: tag_build
needs: upload_src
strategy:
matrix:
include:
- { target: linux-64, os: ubuntu-22.04 }
- { target: linux-arm64, os: ubuntu-22.04-arm }
- { target: osx-64, os: macos-13 }
- { target: osx-64, os: macos-15-intel }
- { target: osx-arm64, os: macos-latest }
- { target: win-64, os: windows-latest }
fail-fast: false
@@ -88,10 +95,8 @@ jobs:
- name: Set Platform Environment Variables
shell: bash -l {0}
env:
BUILD_TAG: ${{ needs.tag_build.outputs.build_tag }}
OPERATING_SYSTEM: ${{ runner.os }}
run: |
echo "BUILD_TAG=${BUILD_TAG}" >> "$GITHUB_ENV"
if [[ $OPERATING_SYSTEM == 'Windows' ]]; then
echo 'PIXI_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV"
echo 'RATTLER_CACHE_DIR=D:\rattler' >> "$GITHUB_ENV"
@@ -100,6 +105,7 @@ jobs:
- name: Checkout Source
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
ref: ${{ github.sha }}
fetch-depth: 2
fetch-tags: true
submodules: 'recursive'
@@ -110,6 +116,7 @@ jobs:
cache: false
- name: Install the Apple certificate and provisioning profile
id: get_cert
if: runner.os == 'macOS'
env:
APP_SPECIFIC_PASSWORD: ${{ secrets.APP_SPECIFIC_PASSWORD }}
@@ -120,9 +127,15 @@ jobs:
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
run: |
if [ -z "$BUILD_CERTIFICATE_BASE64" ]; then
echo "has_cert=false" >> $GITHUB_OUTPUT
echo "No certificate avalable... skipping" && exit 0
else
echo "has_cert=true" >> $GITHUB_OUTPUT
fi
# create variables
CERTIFICATE_PATH=$RUNNER_TEMP/build_certificate.p12
PP_PATH=$RUNNER_TEMP/FreeCAD_Weekly.provisionprofile
PP_PATH=$RUNNER_TEMP/FreeCAD_bundle.provisionprofile
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
export KEYCHAIN_PASSWORD=$(openssl rand -base64 8)
@@ -152,12 +165,13 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
SIGNING_KEY_ID: ${{ secrets.SIGNING_KEY_ID }}
SIGN_RELEASE: "true"
SIGN_RELEASE: ${{ steps.get_cert.outputs.has_cert }}
TARGET_PLATFORM: ${{ matrix.target }}
MAKE_INSTALLER: "true"
UPLOAD_RELEASE: "true"
BUILD_TAG: ${{ needs.upload_src.outputs.build_tag }}
run: |
python3 package/rattler-build/scripts/make_version_file.py ../freecad_version.txt
git apply package/rattler-build/scripts/disable_git_info.patch
python3 package/scripts/write_version_info.py ../freecad_version.txt
cd package/rattler-build
pixi install
pixi run -e package create_bundle

View File

@@ -24,4 +24,4 @@ jobs:
- name: 'Checkout Repository'
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
- name: 'Dependency Review'
uses: actions/dependency-review-action@56339e523c0409420f6c2c9a2f4292bbb3c07dd3 # v4
uses: actions/dependency-review-action@3c4e3dcb1aa7874d2c16be7d79418e9b7efd6261 # v4

View File

@@ -8,7 +8,10 @@ on:
jobs:
trigger-copr-build:
environment: fedora-daily
env:
copr_login: ${{ secrets.COPR_LOGIN }}
copr_username: ${{ secrets.COPR_USERNAME }}
copr_token: ${{ secrets.COPR_TOKEN }}
runs-on: ubuntu-latest
container: quay.io/packit/packit
@@ -16,7 +19,13 @@ jobs:
- name: setup copr token
run: |
mkdir -p ~/.config
echo "$copr_token" > ~/.config/copr
echo \
"[copr-cli]
login = $copr_login
username = $copr_username
token = $copr_token
copr_url = https://copr.fedorainfracloud.org
" > ~/.config/copr
- name: checkout sources
uses: actions/checkout@v4
with:

View File

@@ -13,6 +13,7 @@ jobs:
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8
with:
fetch-depth: 0
token: ${{ secrets.GH_TOKEN_FOR_CROWDIN_SYNC }}
- name: Install Qt ≥ 6.8
uses: jurplel/install-qt-action@d325aaf2a8baeeda41ad0b5d39f84a6af9bcf005
@@ -45,8 +46,8 @@ jobs:
- name: Commit changes
run: |
git config --global user.name "github-actions"
git config --global user.email "github-actions@github.com"
git config --global user.name "freecad-gh-actions-translation-bot"
git config --global user.email "freecad-gh-actions-translation-bot@github.com"
git add src
git commit -m "Update translations from Crowdin" || echo "No changes to commit"
@@ -56,12 +57,11 @@ jobs:
git push origin update-crowdin-translations --force
- name: Create Pull Request
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412
with:
branch: update-crowdin-translations
title: "Update translations from Crowdin"
body: "Automatic Crowdin update."
labels: "translations, automated"
token: ${{ secrets.GH_TOKEN_FOR_CROWDIN_SYNC }}
delete-branch: true
add-paths: |

View File

@@ -35,7 +35,7 @@ jobs:
echo "last_month=$first_day..$last_day" >> "$GITHUB_ENV"
- name: Run issue-metrics tool
uses: github/issue-metrics@637a24e71b78bc10881e61972b19ea9ff736e14a # v3.25.2
uses: github/issue-metrics@78b1d469a1b1c94945b15bd71dedcb1928667f49 # v3.25.3
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SEARCH_QUERY: 'repo:FreeCAD/FreeCAD is:issue created:${{ env.last_month }}'

View File

@@ -62,7 +62,7 @@ jobs:
run: |
mkdir -p ${{ env.artifactsDownloadDir }}
- name: Download artifacts
uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0
uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
with:
path: ${{ env.artifactsDownloadDir }}
- name: Save input data to file

View File

@@ -3,7 +3,7 @@ files_to_sync:
- .packit.yaml
actions:
post-upstream-clone:
- bash -c 'BUILD_TAG=dev /usr/bin/python3 package/rattler-build/scripts/make_version_file.py freecad_version.txt'
- bash -c '/usr/bin/python3 package/scripts/write_version_info.py freecad_version.txt'
- rm -f freecad-sources.tar.gz
changelog-entry:
- bash -c 'git log --no-merges --pretty="format:- %s (%an)" $(git describe --tags --abbrev=0 )..HEAD -- |sed 's/%/%%/g''

View File

@@ -82,15 +82,11 @@ persistent=yes
# Minimum Python version to use for version dependent checks. Will default to
# the version used to run pylint.
py-version=3.8
py-version=3.10
# Discover python modules and packages in the file system subtree.
recursive=no
# When enabled, pylint would attempt to guess common misconfiguration and emit
# user-friendly hints instead of false-positive error messages.
suggestion-mode=yes
# Allow loading of arbitrary C extensions. Extensions are imported into the
# active Python interpreter and may run arbitrary code.
unsafe-load-any-extension=no

View File

@@ -71,10 +71,6 @@
"type": "BOOL",
"value": "ON"
},
"BUILD_REVERSEENGINEERING": {
"type": "BOOL",
"value": "OFF"
},
"ENABLE_DEVELOPER_TESTS": {
"type": "BOOL",
"value": "ON"

5
package/WindowsInstaller/.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
*.exe
*-SHA256.txt
MSVCRedist
FreeCAD
version.nsh

View File

@@ -0,0 +1,47 @@
del /S *_d.*
del /S *_debug.*
del /S *.pyc
del /S *.pdb
del /S boost*-gd-*.dll
cd bin
del assistant.exe
del Coin4d.dll
del designer.exe
del freetyped.dll
del libcrypto-3d.dll
del libEGLd.dll
del libGLESv2d.dll
del libssl-3d.dll
del linguist.exe
del qdoc.exe
del Qt6Concurrentd.dll
del Qt6Cored.dll
del Qt6DBusd.dll
del Qt6DesignerComponentsd.dll
del Qt6Designerd.dll
del Qt6Guid.dll
del Qt6Helpd.dll
del Qt6LabsAnimationd.dll
del Qt6LabsFolderListModeld.dll
del Qt6LabsPlatformd.dll
del Qt6LabsSettingsd.dll
del Qt6LabsSharedImaged.dll
del Qt6LabsWavefrontMeshd.dll
del Qt6MultimediaWidgetsd.dll
del Qt6Multimediad.dll
del Qt6Networkd.dll
del Qt6OpenGLWidgetsd.dll
del Qt6OpenGLd.dll
del Qt6PrintSupportd.dll
del Qt6SpatialAudiod.dll
del Qt6Sqld.dll
del Qt6SvgWidgetsd.dll
del Qt6Svgd.dll
del Qt6Testd.dll
del Qt6UiToolsd.dll
del Qt6Widgetsd.dll
del Qt6Xmld.dll
del QtWebEngineProcessd.exe
del Quarter1d.dll
del xerces-c_3_2D.dll
del zlibd.dll

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,62 @@
/*
Settings for FreeCAD installer
These typically need to be modified for each FreeCAD release
*/
# Make the installer as small as possible
# Using /SOLID is usually better for file size but it can't be used if the original size is
# more than 2GB, if building with /SOLID fails try disabling it
# comment this or use /DFC_TEST_BUILD command line option for testing builds since it will reduce
# the time to create an installer a lot at the cost of a much greater file size.
# So assure it is active for release builds!
!ifndef FC_TEST_BUILD
SetCompressor /SOLID lzma
!endif
#--------------------------------
# File locations
# !!! you may need to adjust them to the folders in your Windows system !!!
# can be specified with /D command line argument to makensis.exe
!ifndef FILES_FREECAD
!define FILES_FREECAD "${__FILEDIR__}\FreeCAD"
!endif
!ifndef FILES_THUMBS
!define FILES_THUMBS "${__FILEDIR__}\thumbnail"
!endif
# msvc redistributables location is required for LibPack builds but not conda
# when using a LibPack build set the redistributables directory location here
# or with /D command line argument to makensis.exe
#!define FILES_DEPS "${__FILEDIR__}\MSVCRedist"
#--------------------------------
# get version info from freecadcmd
!system "${FILES_FREECAD}\bin\freecadcmd.exe --safe-mode -c $\"import datetime; print(f'!define COPYRIGHT_YEAR {datetime.date.today().year}')$\">${__FILEDIR__}\version.nsh" = 0
!system "${FILES_FREECAD}\bin\freecadcmd.exe --safe-mode -c $\"print(f'!define APP_VERSION_MAJOR \$\"{App.Version()[0]}\$\"')$\">>${__FILEDIR__}\version.nsh" = 0
!system "${FILES_FREECAD}\bin\freecadcmd.exe --safe-mode -c $\"print(f'!define APP_VERSION_MINOR \$\"{App.Version()[1]}\$\"')$\">>${__FILEDIR__}\version.nsh" = 0
!system "${FILES_FREECAD}\bin\freecadcmd.exe --safe-mode -c $\"print(f'!define APP_VERSION_PATCH \$\"{App.Version()[2]}\$\"')$\">>${__FILEDIR__}\version.nsh" = 0
!system "${FILES_FREECAD}\bin\freecadcmd.exe --safe-mode -c $\"print(f'!define APP_VERSION_REVISION \$\"{App.Version()[3].split()[0]}\$\"')$\">>${__FILEDIR__}\version.nsh" = 0
!include "${__FILEDIR__}\version.nsh"
!delfile "${__FILEDIR__}\version.nsh"
!define APP_VERSION_EMERGENCY "" # use "1" for an emergency release of FreeCAD otherwise ""
# alternatively you can use APP_VERSION_EMERGENCY for a custom suffix of the version number
!define APP_EMERGENCY_DOT "" # use "." for an emergency release of FreeCAD otherwise ""
!define APP_VERSION_BUILD 1 # Start with 1 for the installer releases of each version
!define APP_VERSION "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}${APP_EMERGENCY_DOT}${APP_VERSION_EMERGENCY}" # Version to display
#--------------------------------
# Installer file name
# Typical names for the release are "FreeCAD-020-Installer-1.exe" etc.
!ifndef ExeFile
!define ExeFile "${APP_NAME}_${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}${APP_VERSION_EMERGENCY}-Windows-x86_64-installer-${APP_VERSION_BUILD}.exe"
!endif
#--------------------------------
# installer bit type - FreeCAD is only provided as 64bit build
!define MULTIUSER_USE_PROGRAMFILES64

View File

@@ -1 +1,2 @@
signtool.exe sign /f FCweborg.pfx /p FreeCADIsCool /fd sha512 /tr http://timestamp.digicert.com /td sha512 /v %1
certutil -hashfile %1 SHA256 > %1-SHA256.txt

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View File

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

View File

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

View File

Before

Width:  |  Height:  |  Size: 91 KiB

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -13,13 +13,13 @@ Configuration and variables of FreeCAD installer
# Names and version
!define APP_NAME "FreeCAD"
!define APP_VERSION_NUMBER "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_REVISION}.${APP_VERSION_BUILD}"
!define APP_VERSION_NUMBER "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}.${APP_VERSION_BUILD}"
# For the proposed install folder we use the scheme "FreeCAD 0.18"
# however for the Registry, we need the scheme "FreeCAD 0.18.x" in order
# to check if it is exactly this version (to support side-by-side installations)
!define APP_SERIES_NAME "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}"
!define APP_SERIES_KEY "${APP_VERSION_MAJOR}${APP_VERSION_MINOR}${APP_VERSION_REVISION}${APP_VERSION_EMERGENCY}"
!define APP_SERIES_KEY2 "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_REVISION}${APP_EMERGENCY_DOT}${APP_VERSION_EMERGENCY}"
!define APP_SERIES_KEY "${APP_VERSION_MAJOR}${APP_VERSION_MINOR}${APP_VERSION_PATCH}${APP_VERSION_EMERGENCY}"
!define APP_SERIES_KEY2 "${APP_VERSION_MAJOR}.${APP_VERSION_MINOR}.${APP_VERSION_PATCH}${APP_EMERGENCY_DOT}${APP_VERSION_EMERGENCY}"
!define APP_DIR_REGKEY "Software\Microsoft\Windows\CurrentVersion\App Paths\${APP_NAME}.exe"
!define APP_DIR "${APP_NAME} ${APP_SERIES_NAME}"
# Fixme: FC should use different preferences folder for every release
@@ -60,7 +60,7 @@ Configuration and variables of FreeCAD installer
!define SETUP_ICON "icons\FreeCAD.ico"
!define SETUP_HEADERIMAGE "graphics\header.bmp"
!define SETUP_WIZARDIMAGE "graphics\orange.bmp"
!define SETUP_WIZARDIMAGE "graphics\banner.bmp"
!define SETUP_UNINSTALLER "Uninstall-${APP_NAME}.exe"
!define SETUP_UNINSTALLER_KEY "${APP_NAME}${APP_SERIES_KEY}"

View File

@@ -92,7 +92,7 @@ BrandingText " "
VIProductVersion "${APP_VERSION_NUMBER}"
VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductName" "${APP_NAME}"
VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "${APP_DIR}.${APP_VERSION_REVISION}"
VIAddVersionKey /LANG=${LANG_ENGLISH} "ProductVersion" "${APP_DIR}.${APP_VERSION_PATCH}"
VIAddVersionKey /LANG=${LANG_ENGLISH} "FileDescription" "${APP_INFO}"
VIAddVersionKey /LANG=${LANG_ENGLISH} "FileVersion" "${APP_VERSION}"
VIAddVersionKey /LANG=${LANG_ENGLISH} "LegalCopyright" "${APP_COPYRIGHT}"

View File

@@ -50,7 +50,7 @@ Function PostMultiUserPageInit
# check if there is an existing FreeCAD installation of the same FreeCAD series
# we usually don't release more than 10 versions so with 20 we are safe to check if a newer version is installed
IntOp $4 ${APP_VERSION_REVISION} + 20
IntOp $4 ${APP_VERSION_PATCH} + 20
${for} $5 0 $4
ReadRegStr $0 SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${APP_NAME}${APP_VERSION_MAJOR}${APP_VERSION_MINOR}$5" "DisplayVersion"
# also check for an emergency release
@@ -110,7 +110,8 @@ Function .onInit
${orif} $R0 == "5.1" # XP
${orif} $R0 == "5.2" # 2003
${orif} $R0 == "6.0" # Vista
MessageBox MB_OK|MB_ICONSTOP "${APP_NAME} ${APP_VERSION} requires Windows 7 or newer." /SD IDOK
${orif} $R0 == "6.1" # 7
MessageBox MB_OK|MB_ICONSTOP "${APP_NAME} ${APP_VERSION} requires Windows 8 or newer." /SD IDOK
Quit
${endif}
@@ -130,7 +131,7 @@ Function .onInit
# plugin must be unloaded
${nsProcess::Unload}
# initialize the multi-uder installer UI
# initialize the multi-user installer UI
!insertmacro MULTIUSER_INIT
# this can be reset to "true" in section SecDesktop

View File

@@ -18,8 +18,8 @@
# and returns the number of the character in the FindStr where the SearchStr was found (Pointer)
# if nothing was found or the search is impossible the Pointer is set to -1
StrLen $R2 ${SearchStr}
StrLen $R4 ${FindStr}
StrLen $R2 "${SearchStr}"
StrLen $R4 "${FindStr}"
StrCpy $R5 0
${if} $R2 == 0
${orif} $R4 == 0
@@ -27,8 +27,8 @@
${endif}
IntCmp $R4 $R2 loopA NotFound
loopA:
StrCpy $R3 ${FindStr} $R2 $R5
StrCmp $R3 ${SearchStr} Found
StrCpy $R3 "${FindStr}" $R2 $R5
StrCmp $R3 "${SearchStr}" Found
IntOp $R5 $R5 + 1
IntCmp $R4 $R5 loopA NotFound
Goto loopA

View File

@@ -7,7 +7,7 @@ Language: English
${LangFileString} TEXT_INSTALL_CURRENTUSER "(Installed for Current User)"
${LangFileString} TEXT_WELCOME "This wizard will guide you through the installation of $(^NameDA), $\r$\n\
${LangFileString} TEXT_WELCOME "This wizard will guide you through the installation of $(^NameDA). $\r$\n\
$\r$\n\
$_CLICK"

View File

@@ -8,14 +8,15 @@ Installation of program files, dictionaries and external components
#--------------------------------
# Program files
!include LogicLib.nsh
Section -ProgramFiles SecProgramFiles
# if the $INSTDIR does not contain "FreeCAD" we must add a subfolder to avoid that FreeCAD will e.g.
# be installed directly to C:\programs - the uninstaller will then delete the whole
# C:\programs directory
StrCpy $String $INSTDIR
StrCpy $Search ${APP_NAME}
StrCpy $String "$INSTDIR"
StrCpy $Search "${APP_NAME}"
Call StrPoint # function from Utils.nsh
${if} $Pointer == "-1"
StrCpy $INSTDIR "$INSTDIR\${APP_DIR}"
@@ -38,8 +39,11 @@ Section -ProgramFiles SecProgramFiles
File /r "${FILES_FREECAD}\bin\*.*"
# MSVC redistributable DLLs
SetOutPath "$INSTDIR\bin"
File "${FILES_DEPS}\*.*"
!ifdef FILES_DEPS
!echo "Including MSVC Redist files from ${FILES_DEPS}"
SetOutPath "$INSTDIR\bin"
File "${FILES_DEPS}\*.*"
!endif
# Others
SetOutPath "$INSTDIR\data"
@@ -52,10 +56,6 @@ Section -ProgramFiles SecProgramFiles
File /r "${FILES_FREECAD}\lib\*.*"
SetOutPath "$INSTDIR\Mod"
File /r "${FILES_FREECAD}\Mod\*.*"
SetOutPath "$INSTDIR\resources"
File /r "${FILES_FREECAD}\resources\*.*"
SetOutPath "$INSTDIR\translations"
File /r "${FILES_FREECAD}\translations\*.*"
SetOutPath "$INSTDIR"
File /r "${FILES_THUMBS}"

12
package/rattler-build/.gitignore vendored Normal file
View File

@@ -0,0 +1,12 @@
linux/AppDir/usr
linux/AppDir/*.desktop
linux/AppDir/*.svg
linux/AppDir/packages.txt
windows/FreeCAD_*Windows*
osx/FreeCAD.app
**.AppImage
**.AppImage.zsync
**.dmg
**.7z
**.exe
**-SHA256.txt

View File

@@ -1,7 +1,7 @@
#!/bin/bash
set -e
set -x
set -x
conda_env="AppDir/usr"
@@ -9,9 +9,6 @@ mkdir -p ${conda_env}
cp -a ../.pixi/envs/default/* ${conda_env}
export PATH="${PWD}/${conda_env}/bin:${PATH}"
export CONDA_PREFIX="${PWD}/${conda_env}"
echo -e "\nDelete unnecessary stuff"
rm -rf ${conda_env}/include
find ${conda_env} -name \*.a -delete
@@ -49,7 +46,7 @@ rm -rf ${conda_env}/lib/cmake/
find . -name "*.h" -type f -delete
find . -name "*.cmake" -type f -delete
python_version=$(python -c 'import platform; print("py" + platform.python_version_tuple()[0] + platform.python_version_tuple()[1])')
python_version=$(${conda_env}/bin/python -c 'import platform; print("py" + platform.python_version_tuple()[0] + platform.python_version_tuple()[1])')
version_name="FreeCAD_${BUILD_TAG}-Linux-$(uname -m)-${python_version}"
echo -e "\################"
@@ -62,6 +59,20 @@ sed -i "1s/.*/\nLIST OF PACKAGES:/" AppDir/packages.txt
curl -LO https://github.com/AppImage/appimagetool/releases/download/continuous/appimagetool-$(uname -m).AppImage
chmod a+x appimagetool-$(uname -m).AppImage
if [ "${UPLOAD_RELEASE}" == "true" ]; then
case "${BUILD_TAG}" in
*weekly*)
GH_UPDATE_TAG="weeklies"
;;
*rc*)
GH_UPDATE_TAG="${BUILD_TAG}"
;;
*)
GH_UPDATE_TAG="latest"
;;
esac
fi
echo -e "\nCreate the appimage"
# export GPG_TTY=$(tty)
chmod a+x ./AppDir/AppRun
@@ -69,7 +80,7 @@ chmod a+x ./AppDir/AppRun
--comp zstd \
--mksquashfs-opt -Xcompression-level \
--mksquashfs-opt 22 \
-u "gh-releases-zsync|FreeCAD|FreeCAD|${BUILD_TAG}|FreeCAD*$(uname -m)*.AppImage.zsync" \
-u "gh-releases-zsync|FreeCAD|FreeCAD|${GH_UPDATE_TAG}|FreeCAD*$(uname -m)*.AppImage.zsync" \
AppDir ${version_name}.AppImage
# -s --sign-key ${GPG_KEY_ID} \
@@ -77,5 +88,13 @@ echo -e "\nCreate hash"
sha256sum ${version_name}.AppImage > ${version_name}.AppImage-SHA256.txt
if [ "${UPLOAD_RELEASE}" == "true" ]; then
gh release upload --clobber ${BUILD_TAG} "${version_name}.AppImage" "${version_name}.AppImage-SHA256.txt"
gh release upload --clobber ${BUILD_TAG} "${version_name}.AppImage" "${version_name}.AppImage.zsync" "${version_name}.AppImage-SHA256.txt"
if [ "${GH_UPDATE_TAG}" == "weeklies" ]; then
generic_name="FreeCAD_weekly-Linux-$(uname -m)"
mv "${version_name}.AppImage" "${generic_name}.AppImage"
mv "${version_name}.AppImage.zsync" "${generic_name}.AppImage.zsync"
mv "${version_name}.AppImage-SHA256.txt" "${generic_name}.AppImage-SHA256.txt"
gh release create weeklies --prerelease | true
gh release upload --clobber weeklies "${generic_name}.AppImage" "${generic_name}.AppImage.zsync" "${generic_name}.AppImage-SHA256.txt"
fi
fi

View File

@@ -9,9 +9,6 @@ mkdir -p ${conda_env}
cp -a ../.pixi/envs/default/* ${conda_env}
export PATH="${PWD}/${conda_env}/bin:${PATH}"
export CONDA_PREFIX="${PWD}/${conda_env}"
# delete unnecessary stuff
rm -rf ${conda_env}/include
find ${conda_env} -name \*.a -delete
@@ -50,7 +47,7 @@ cmake --build build
mkdir -p FreeCAD.app/Contents/MacOS
cp build/FreeCAD FreeCAD.app/Contents/MacOS/FreeCAD
python_version=$(python -c 'import platform; print("py" + platform.python_version_tuple()[0] + platform.python_version_tuple()[1])')
python_version=$(${conda_env}/bin/python -c 'import platform; print("py" + platform.python_version_tuple()[0] + platform.python_version_tuple()[1])')
version_name="FreeCAD_${BUILD_TAG}-macOS-$(uname -m)-${python_version}"
application_menu_name="FreeCAD_${BUILD_TAG}"
@@ -71,7 +68,7 @@ rm -rf ${conda_env}/Library
if [[ "${SIGN_RELEASE}" == "true" ]]; then
# create the signed dmg
./macos_sign_and_notarize.zsh -p "FreeCAD" -k ${SIGNING_KEY_ID} -o "${version_name}.dmg"
../../scripts/macos_sign_and_notarize.zsh -p "FreeCAD" -k ${SIGNING_KEY_ID} -o "${version_name}.dmg"
else
# create the dmg
dmgbuild -s dmg_settings.py "FreeCAD" "${version_name}.dmg"

View File

@@ -1,3 +1,5 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
# Ensure default values are set in defines if they are not already provided
defines.setdefault('containing_folder', '.')
defines.setdefault('app_name', 'FreeCAD.app')

File diff suppressed because it is too large Load Diff

View File

@@ -22,44 +22,37 @@ freecad = { path = "." }
[feature.package.dependencies]
python = ">=3.11,<3.12"
[feature.package.tasks]
create_bundle = 'bash -c "cd $(bash scripts/get_os.bash) && bash create_bundle.sh"'
## Linux (x86-64)
[feature.package.target.linux-64.dependencies]
coreutils = "*"
[feature.package.target.linux-64.tasks]
create_bundle = 'bash -c "cd linux && bash create_bundle.sh"'
## Linux (aarch64)
[feature.package.target.linux-aarch64.dependencies]
coreutils = "*"
[feature.package.target.linux-aarch64.tasks]
create_bundle = 'bash -c "cd linux && bash create_bundle.sh"'
## macOS (Intel)
[feature.package.target.osx-64.dependencies]
coreutils = "*"
dmgbuild = "*"
pyobjc-framework-Quartz = "*"
sed = "*"
[feature.package.target.osx-64.tasks]
create_bundle = 'bash -c "cd osx && bash create_bundle.sh"'
## macOS (Apple Silicon)
[feature.package.target.osx-arm64.dependencies]
coreutils = "*"
dmgbuild = "*"
pyobjc-framework-Quartz = "*"
sed = "*"
[feature.package.target.osx-arm64.tasks]
create_bundle = 'bash -c "cd osx && bash create_bundle.sh"'
## Windows dependencies (x86-64)
[feature.package.target.win-64.dependencies]
git = "*"
[feature.package.target.win-64.tasks]
create_bundle = 'bash -c "cd windows && bash create_bundle.sh"'
nsis = { version = "*", build = "*_log*" }
7zip = "*"
vs2022_win-64 = "*"
[environments]
default = ["freecad"]

View File

@@ -145,7 +145,6 @@ requirements:
- gmsh
- graphviz
- ifcopenshell
- jinja2
- lark
- lxml
- matplotlib-base

View File

@@ -1,13 +0,0 @@
diff --git a/src/Tools/SubWCRev.py b/src/Tools/SubWCRev.py
index 1f3f0a436343..c314003736f1 100644
--- a/src/Tools/SubWCRev.py
+++ b/src/Tools/SubWCRev.py
@@ -523,7 +523,7 @@ def main():
inp = open("%s/src/Build/Version.h.in" % (bindir))
lines = inp.readlines()
inp.close()
- lines = i.writeVersion(lines)
+ #lines = i.writeVersion(lines)
out = open("%s/src/Build/Version.h.out" % (bindir), "w")
out.writelines(lines)
out.write("\n")

View File

@@ -1,53 +0,0 @@
import sys
import os
import subprocess
import platform
from datetime import datetime
import freecad
import FreeCAD
package_manager = "conda"
system = platform.platform().split("-")[0]
arch = platform.machine()
# Windows uses a different syntax
if arch == "AMD64":
arch = "x86_64"
if "ARCH" in os.environ:
if os.environ["ARCH"] != "":
arch = os.environ["ARCH"]
python_version = platform.python_version().split(".")
python_version = "py" + python_version[0] + python_version[1]
date = str(datetime.now()).split(" ")[0]
version_info = FreeCAD.Version()
build_version_suffix = FreeCAD.ConfigGet("BuildVersionSuffix")
dev_version = version_info[0] + "." + version_info[1] + "." + version_info[2] + build_version_suffix
revision = version_info[3].split(" ")[0]
if system == "macOS":
import jinja2
print("create plist from template")
osx_directory = os.path.join(os.path.dirname(__file__), "..", "osx")
with open(os.path.join(osx_directory, "Info.plist.template")) as template_file:
template_str = template_file.read()
template = jinja2.Template(template_str)
rendered_str = template.render( FREECAD_VERSION="{}-{}".format(dev_version, revision),
APPLICATION_MENU_NAME="FreeCAD-{}-{}".format(dev_version, revision) )
with open(os.path.join(osx_directory, "FreeCAD.app", "Contents", "Info.plist"), "w") as rendered_file:
rendered_file.write(rendered_str)
if "DEPLOY_RELEASE" in os.environ and os.environ["DEPLOY_RELEASE"] == "weekly-builds":
dev_version = "weekly-builds"
revision_separator = "-"
else:
revision_separator = ""
revision = ""
bundle_name = f"FreeCAD_{dev_version}{revision_separator}{revision}-{package_manager}-{system}-{arch}-{python_version}"
with open("bundle_name.txt", "w") as bundle_name_file:
bundle_name_file.write(bundle_name)

View File

@@ -0,0 +1,8 @@
#!/usr/bin/env bash
if [[ "$OSTYPE" =~ (msys*|cygwin*|mingw*) ]]; then
echo windows
elif [[ "$OSTYPE" == darwin* ]]; then
echo osx
elif [[ "$OSTYPE" == linux* ]]; then
echo linux
fi

View File

@@ -1,56 +0,0 @@
set conda_env="fc_env"
robocopy ..\.pixi\envs\default\* %conda_env% /S /MT:%NUMBER_OF_PROCESSORS% > nul
%conda_env%\python ..\scripts\get_freecad_version.py
set /p freecad_version_name= <bundle_name.txt
echo **********************
echo %freecad_version_name%
echo **********************
REM remove arm binaries that fail to extract unless using latest 7zip
for /r %conda_env% %%i in (*arm*.exe) do (@echo "%%i will be removed" & @del "%%i")
set copy_dir="FreeCAD_Conda_Build"
mkdir %copy_dir%
REM Copy Conda's Python and (U)CRT to FreeCAD/bin
robocopy %conda_env%\DLLs %copy_dir%\bin\DLLs /S /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Lib %copy_dir%\bin\Lib /XD __pycache__ /S /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Scripts %copy_dir%\bin\Scripts /S /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\python*.* %copy_dir%\bin\ /XF *.pdb /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\msvc*.* %copy_dir%\bin\ /XF *.pdb /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\ucrt*.* %copy_dir%\bin\ /XF *.pdb /MT:%NUMBER_OF_PROCESSORS% > nul
REM Copy meaningful executables
robocopy %conda_env%\Library\bin %copy_dir%\bin\ ccx.exe /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\bin %copy_dir%\bin\ gmsh.exe /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\bin %copy_dir%\bin\ dot.exe /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\bin %copy_dir%\bin\ unflatten.exe /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\mingw-w64\bin * %copy_dir%\bin\ /MT:%NUMBER_OF_PROCESSORS% > nul
REM Copy Conda's QT5/plugins to FreeCAD/bin
robocopy %conda_env%\Library\plugins %copy_dir%\bin\ /S /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\resources %copy_dir%\resources /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\translations %copy_dir%\translations /MT:%NUMBER_OF_PROCESSORS% > nul
REM get all the dependency .dlls
robocopy %conda_env%\Library\bin *.dll %copy_dir%\bin /XF *.pdb /XF api*.* /MT:%NUMBER_OF_PROCESSORS% > nul
REM Copy FreeCAD build
robocopy %conda_env%\Library\bin FreeCAD* %copy_dir%\bin /XF *.pdb /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\data %copy_dir%\data /XF *.txt /S /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\Ext %copy_dir%\Ext /S /XD __pycache__ /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\lib %copy_dir%\lib /XF *.lib /XF *.prl /XF *.sh /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\Mod %copy_dir%\Mod /S /XD __pycache__ /MT:%NUMBER_OF_PROCESSORS% > nul
robocopy %conda_env%\Library\doc %copy_dir%\doc ThirdPartyLibraries.html LICENSE.html /MT:%NUMBER_OF_PROCESSORS% > nul
REM Apply Patches
rename %copy_dir%\bin\Lib\ssl.py ssl-orig.py
copy ssl-patch.py %copy_dir%\bin\Lib\ssl.py
cd %copy_dir%\..
ren %copy_dir% %freecad_version_name%
dir
REM if errorlevel1 exit 1
"%ProgramFiles%\7-Zip\7z.exe" a -t7z -mx9 -mmt=%NUMBER_OF_PROCESSORS% %freecad_version_name%.7z %freecad_version_name%\ -bb
certutil -hashfile "%freecad_version_name%.7z" SHA256 > "%freecad_version_name%.7z"-SHA256.txt
echo %date%-%time% >>"%freecad_version_name%.7z"-SHA256.txt

View File

@@ -3,23 +3,9 @@
set -e
set -x
conda_env="fc_env"
conda_env="$(pwd)/../.pixi/envs/default/"
mkdir -p ${conda_env}
cp -a ../.pixi/envs/default/* ${conda_env}
export PATH="${PWD}/${conda_env}/bin:${PATH}"
export CONDA_PREFIX="${PWD}/${conda_env}"
# remove arm binaries that fail to extract unless using latest 7zip
rm $(find ${conda_env} -name \*arm\*.exe)
# delete unnecessary stuff
rm -rf ${conda_env}/include
find ${conda_env} -name \*.a -delete
copy_dir="FreeCAD_Conda_Build"
copy_dir="FreeCAD_Windows"
mkdir -p ${copy_dir}/bin
# Copy Conda's Python and (U)CRT to FreeCAD/bin
@@ -48,16 +34,28 @@ cp -a ${conda_env}/Library/lib ${copy_dir}/lib
cp -a ${conda_env}/Library/Mod ${copy_dir}/Mod
mkdir -p ${copy_dir}/doc
cp -a ${conda_env}/Library/doc/{ThirdPartyLibraries.html,LICENSE.html} ${copy_dir}/doc
rm -rf ${conda_env}/bin_tmp
# delete unnecessary stuff
find ${copy_dir} -name \*.a -delete
find ${copy_dir} -name \*.lib -delete
find ${copy_dir} -name \*arm\*.exe -delete # arm binaries that fail to extract unless using latest 7zip
# Apply Patches
mv ${copy_dir}/bin/Lib/ssl.py ssl-orig.py
mv ${copy_dir}/bin/Lib/ssl.py .ssl-orig.py
cp ssl-patch.py ${copy_dir}/bin/Lib/ssl.py
echo '[Paths]' >> ${copy_dir}/bin/qt6.conf
echo 'Prefix = ../lib/qt6' >> ${copy_dir}/bin/qt6.conf
python_version=$(python -c 'import platform; print("py" + platform.python_version_tuple()[0] + platform.python_version_tuple()[1])')
# convenient shortcuts to run the binaries
if [ -x /c/ProgramData/chocolatey/tools/shimgen.exe ]; then
pushd ${copy_dir}
/c/ProgramData/chocolatey/tools/shimgen.exe -p bin/freecadcmd.exe -i "$(pwd)/../../../WindowsInstaller/icons/FreeCAD.ico" -o "$(pwd)/FreeCADCmd.exe"
/c/ProgramData/chocolatey/tools/shimgen.exe --gui -p bin/freecad.exe -i "$(pwd)/../../../WindowsInstaller/icons/FreeCAD.ico" -o "$(pwd)/FreeCAD.exe"
popd
fi
python_version=$("${copy_dir}"/bin/python.exe -c 'import platform; print("py" + platform.python_version_tuple()[0] + platform.python_version_tuple()[1])')
version_name="FreeCAD_${BUILD_TAG}-Windows-$(uname -m)-${python_version}"
echo -e "################"
@@ -69,11 +67,37 @@ sed -i '1s/.*/\nLIST OF PACKAGES:/' ${copy_dir}/packages.txt
mv ${copy_dir} ${version_name}
"${PROGRAMFILES}/7-Zip/7z.exe" a -t7z -mx9 -mmt=${NUMBER_OF_PROCESSORS} ${version_name}.7z ${version_name} -bb
7z a -t7z -mx9 -mmt=${NUMBER_OF_PROCESSORS} ${version_name}.7z ${version_name} -bb
# create hash
sha256sum ${version_name}.7z > ${version_name}.7z-SHA256.txt
if [ "${MAKE_INSTALLER}" == "true" ]; then
FILES_FREECAD="$(cygpath -w $(pwd))\\${version_name}"
nsis_cpdir=$(pwd)/.nsis_tmp
cp -r "${CONDA_PREFIX}/NSIS" "${nsis_cpdir}"
# curl -L -o ".nsis-log.zip" http://prdownloads.sourceforge.net/nsis/nsis-3.11-log.zip # we use the log variant of the package already
# curl -L -o ".nsis-strlen_8192.zip" "http://prdownloads.sourceforge.net/nsis/nsis-3.11-strlen_8192.zip"
curl -L -o ".NsProcess.7z" "https://nsis.sourceforge.io/mediawiki/images/1/18/NsProcess.zip"
if [ ! $(echo fc19fc66a5219a233570fafd5daeb0c9b85387b379f6df5ac8898159a57c5944 .NsProcess.7z | sha256sum --check --status) ]; then
7z x .NsProcess.7z -o"${nsis_cpdir}" -y
mv "${nsis_cpdir}"/Plugin/nsProcess.dll "${nsis_cpdir}"/Plugins/x86-ansi/nsProcess.dll
mv "${nsis_cpdir}"/Plugin/nsProcessW.dll "${nsis_cpdir}"/Plugins/x86-unicode/nsProcess.dll
"${nsis_cpdir}"/makensis.exe -V4 \
-D"ExeFile=${version_name}-installer.exe" \
-D"FILES_FREECAD=${FILES_FREECAD}" \
-X'SetCompressor /FINAL lzma' \
../../WindowsInstaller/FreeCAD-installer.nsi
mv ../../WindowsInstaller/${version_name}-installer.exe .
sha256sum ${version_name}-installer.exe > ${version_name}-installer.exe-SHA256.txt
else
echo "Error: Failed to get NsProcess plugin. Aborting installer creation..."
fi
rm -rf "${nsis_cpdir}"
fi
if [ "${UPLOAD_RELEASE}" == "true" ]; then
gh release upload --clobber ${BUILD_TAG} "${version_name}.7z" "${version_name}.7z-SHA256.txt"
if [ "${MAKE_INSTALLER}" == "true" ]; then
gh release upload --clobber ${BUILD_TAG} "${version_name}-installer.exe" "${version_name}-installer.exe-SHA256.txt"
fi
fi

View File

@@ -83,8 +83,11 @@ fi
# Check for dmgbuild executable
if ! command -v dmgbuild &> /dev/null; then
echo "Error: dmgbuild not installed. Please install it for example using pip:"
echo 'pip3 install "dmgbuild[badge_icons]>=1.6.0,<1.7.0"'
echo 'Error: dmgbuild not installed. Please install it'
echo '- using pixi:'
echo 'pixi g install dmgbuild --with pyobjc-framework-Quartz'
echo '- using pip:'
echo 'pip3 install dmgbuild pyobjc-framework-Quartz'
exit 1
fi
@@ -119,8 +122,96 @@ run_codesign "${CONTAINING_FOLDER}/${APP_NAME}"
echo "Creating disk image ${DMG_NAME}"
dmgbuild -s ${DMG_SETTINGS} -Dcontaining_folder="${CONTAINING_FOLDER}" -Dapp_name="${APP_NAME}" "${VOLUME_NAME}" "${DMG_NAME}"
# Submit it for notarization (requires that an App Store API Key has been set up in the notarytool)
time xcrun notarytool submit --wait --keychain-profile "${KEYCHAIN_PROFILE}" "${DMG_NAME}"
ID_FILE="${DMG_NAME}.notarization_id"
# Assuming that notarization succeeded, it's a good practice to staple that notarization to the DMG
xcrun stapler staple "${DMG_NAME}"
# Submit it for notarization (requires that an App Store API Key has been set up in the notarytool)
# This is a *very slow* process, and occasionally the GitHub runners lose the internet connection for a short time
# during the run. So in order to be fault-tolerant, this script polls, instead of using --wait
submit_notarization_request() {
if [[ -s "${ID_FILE}" ]]; then
cat "${ID_FILE}"
return
fi
local out
if ! out=$(xcrun notarytool submit --keychain-profile "${KEYCHAIN_PROFILE}" \
--output-format json --no-progress "${DMG_NAME}" 2>&1); then
print -r -- "$out" >&2
return 1
fi
# We asked for JSON output so we had something stable, but of course parsing JSON with ZSH is ugly, so a quick bit of
# Python does it instead...
local id
id=$(print -r -- "$out" |
/usr/bin/python3 -c 'import sys, json; print(json.load(sys.stdin).get("id",""))'
)
[[ -n "$id" ]] || { print -r -- "Could not parse submission id" >&2; return 1; }
print -r -- "$id" > "${ID_FILE}"
print -r -- "$id" # ID is a string here, not an integer, so I can't just return it
}
wait_for_notarization_result() {
local id="$1" attempt=0
while :; do
if xcrun notarytool wait "$id" --keychain-profile "${KEYCHAIN_PROFILE}" \
--timeout 10m --no-progress >/dev/null; then
return 0
fi
(( attempt++ ))
# If the failure was transient (timeout/HTTP/connection) just retry, but make sure to check to see if the problem
# was actually that the signing failed before retrying.
local tmp_json
tmp_json=$(mktemp)
trap 'rm -f "$tmp_json"' EXIT INT TERM
xcrun notarytool info "$id" --keychain-profile "${KEYCHAIN_PROFILE}" --output-format json 2>/dev/null > "$tmp_json"
/usr/bin/python3 - "$tmp_json" <<'PY'
import sys, json
try:
with open(sys.argv[1]) as f:
s = (json.load(f).get("status") or "").lower()
if s in ("invalid", "rejected"):
sys.exit(2)
else:
sys.exit(0)
except Exception:
sys.exit(1)
PY
rc=$?
rm -f "$tmp_json"
if [[ $rc == 2 ]]; then
print -r -- "Notarization was not accepted by Apple:" >&2
xcrun notarytool log "$id" --keychain-profile "${KEYCHAIN_PROFILE}" >&2
return 3
fi
if [[ $attempt -gt 120 ]]; then
print -r -- "🏳️ Notarization is taking too long, bailing out. 🏳️" >&2
return 4
fi
sleep $(( (attempt<6?2**attempt:60) + RANDOM%5 )) # Increasing timeout plus jitter for multi-run safety
done
}
if ! id="$(submit_notarization_request)"; then
print -r -- "❌ Failed to submit notarization request" >&2
exit 1
fi
if [[ -z "$id" ]]; then
print -r -- "❌ Submission succeeded but no ID was returned" >&2
exit 1
fi
print "Notarization submission ID: $id"
if wait_for_notarization_result "$id"; then
print "✅ Notarization succeeded. Stapling..."
xcrun stapler staple "${DMG_NAME}"
print "Stapled: ${DMG_NAME}"
rm -f "${ID_FILE}"
else
rc=$?
print "❌ Notarization failed (code $rc)." >&2
exit "$rc"
fi

View File

@@ -11,12 +11,10 @@ import SubWCRev
gitInfo = SubWCRev.GitControl()
gitInfo.extractInfo("","")
gitDescription = os.environ['BUILD_TAG']
i = open("src/Build/Version.h.cmake")
content = []
for line in i.readlines():
line = line.replace("-${PACKAGE_VERSION_SUFFIX}",gitDescription)
line = line.replace("${PACKAGE_WCREF}",gitInfo.rev)
line = line.replace("${PACKAGE_WCDATE}",gitInfo.date)
line = line.replace("${PACKAGE_WCURL}",gitInfo.url)
@@ -28,6 +26,14 @@ with open("src/Build/Version.h.cmake", "w") as o:
content.append('#define FCRepositoryBranch "%s"\n' % (gitInfo.branch))
o.writelines(content)
with open("src/Tools/SubWCRev.py", "r") as f:
new_subwcrev = f.read()
new_subwcrev = new_subwcrev.replace("lines = i.writeVersion(lines)",
"#lines = i.writeVersion(lines) # this source package already has git info, we do nothing here")
with open("src/Tools/SubWCRev.py", "w") as f:
f.writelines(new_subwcrev)
with open(os.sys.argv[1], "w") as f:
f.write(f"rev_number: {gitInfo.rev}\n")
f.write(f"branch_name: {gitInfo.branch}\n")
@@ -35,8 +41,9 @@ with open(os.sys.argv[1], "w") as f:
f.write(f"commit_hash: {gitInfo.hash}\n")
f.write(f"remote_url: {gitInfo.url}\n")
p = subprocess.Popen(["git", "-c", "user.name='github-actions[bot]'", "-c", "user.email='41898282+github-actions[bot]@users.noreply.github.com'",
"commit", "-a", "-m", "add git information"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p = subprocess.Popen(["git", "-c", "user.name='github-actions[bot]'", "-c",
"user.email='41898282+github-actions[bot]@users.noreply.github.com'", "commit", "-a", "-m",
"add git version information"], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()

View File

@@ -152,24 +152,24 @@ freecad-stubs = "*"
## Qt 6 Configuration Presets
[target.linux-64.tasks]
configure-debug = { cmd = [ "cmake", "--preset", "conda-linux-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-linux-release", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-debug = { cmd = [ "cmake", "--preset", "conda-linux-debug" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-linux-release" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
[target.linux-aarch64.tasks]
configure-debug = { cmd = [ "cmake", "--preset", "conda-linux-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on= ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-linux-release", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on= ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-debug = { cmd = [ "cmake", "--preset", "conda-linux-debug" ], depends-on= ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-linux-release" ], depends-on= ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
[target.osx-64.tasks]
configure-debug = { cmd = [ "cmake", "--preset", "conda-macos-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-macos-release", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-debug = { cmd = [ "cmake", "--preset", "conda-macos-debug" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-macos-release" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
[target.osx-arm64.tasks]
configure-debug = { cmd = [ "cmake", "--preset", "conda-macos-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-macos-release", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-debug = { cmd = [ "cmake", "--preset", "conda-macos-debug" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-macos-release" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
[target.win-64.tasks]
configure-debug = { cmd = [ "cmake", "--preset", "conda-windows-debug", "-DBUILD_REVERSEENGINEERING=OFF", "-DCMAKE_GENERATOR_PLATFORM=", "-DCMAKE_GENERATOR_TOOLSET=" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-windows-release", "-DBUILD_REVERSEENGINEERING=OFF", "-DCMAKE_GENERATOR_PLATFORM=", "-DCMAKE_GENERATOR_TOOLSET=" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-debug = { cmd = [ "cmake", "--preset", "conda-windows-debug", "-DCMAKE_GENERATOR_PLATFORM=", "-DCMAKE_GENERATOR_TOOLSET=" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
configure-release = { cmd = [ "cmake", "--preset", "conda-windows-release", "-DCMAKE_GENERATOR_PLATFORM=", "-DCMAKE_GENERATOR_TOOLSET=" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
freecad-debug = { cmd = [ ".pixi/envs/default/Library/bin/FreeCAD.exe" ], depends-on = ["install-debug"]}
freecad-release = { cmd = [ ".pixi/envs/default/Library/bin/FreeCAD.exe" ], depends-on = ["install-release"]}

View File

@@ -1,38 +1,34 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from Base.PyObjectBase import PyObjectBase
from typing import List
class ApplicationDirectories(PyObjectBase):
"""
App.ApplicationDirectories class.
Provides access to the directory versioning methods of its C++ counterpart.
For the time being this class only provides access to the directory versioning methods of its
C++ counterpart. These are all static methods, so no instance is needed. The main methods of
These are all static methods, so no instance is needed. The main methods of
this class are migrateAllPaths(), usingCurrentVersionConfig(), and versionStringForPath().
Author: Chris Hennes (chennes@pioneerlibrarysystem.org)
Licence: LGPL-2.1-or-later
DeveloperDocu: ApplicationDirectories
"""
@staticmethod
def usingCurrentVersionConfig(path:str) -> bool:
def usingCurrentVersionConfig(path: str, /) -> bool:
"""
usingCurrentVersionConfig(path)
Determine if a given config path is for the current version of the program.
Determine if a given config path is for the current version of the program
path : the path to check
Args:
path: The path to check.
"""
...
@staticmethod
def migrateAllPaths(paths: List[str]) -> None:
def migrateAllPaths(paths: list[str], /) -> None:
"""
migrateAllPaths(paths)
Migrate a set of versionable configuration directories from the given paths to a new version.
Migrate a set of versionable configuration directories from the given paths to a new
version. The new version's directories cannot exist yet, and the old ones *must* exist.
The new version's directories cannot exist yet, and the old ones *must* exist.
If the old paths are themselves versioned, then the new paths will be placed at the same
level in the directory structure (e.g., they will be siblings of each entry in paths).
If paths are NOT versioned, the new (versioned) copies will be placed *inside* the
@@ -41,6 +37,9 @@ class ApplicationDirectories(PyObjectBase):
If the list contains the same path multiple times, the duplicates are ignored, so it is safe
to pass the same path multiple times.
Args:
paths: List of paths to migrate from.
Examples:
Running FreeCAD 1.1, /usr/share/FreeCAD/Config/ -> /usr/share/FreeCAD/Config/v1-1/
Running FreeCAD 1.1, /usr/share/FreeCAD/Config/v1-1 -> raises exception, path exists
@@ -49,60 +48,80 @@ class ApplicationDirectories(PyObjectBase):
...
@staticmethod
def versionStringForPath(major:int, minor:int) -> str:
def versionStringForPath(major: int, minor: int, /) -> str:
"""
versionStringForPath(major, minor) -> str
Given a major and minor version number, return the name for a versioned subdirectory.
Given a major and minor version number, return a string that can be used as the name for a
versioned subdirectory. Only returns the version string, not the full path.
Args:
major: Major version number.
minor: Minor version number.
Returns:
A string that can be used as the name for a versioned subdirectory.
Only returns the version string, not the full path.
"""
...
@staticmethod
def isVersionedPath(startingPath:str) -> bool:
def isVersionedPath(startingPath: str, /) -> bool:
"""
isVersionedPath(startingPath) -> bool
Determine if a given path is versioned.
Determine if a given path is versioned (that is, if its last component contains
something that this class would have created as a versioned subdirectory). Returns true
for any path that the *current* version of FreeCAD would recognized as versioned, and false
for either something that is not versioned, or something that is versioned but for a later
version of FreeCAD.
That is, if its last component contains something that this class would have
created as a versioned subdirectory).
Args:
startingPath: The path to check.
Returns:
True for any path that the *current* version of FreeCAD would recognize as versioned,
and False for either something that is not versioned, or something that is versioned
but for a later version of FreeCAD.
"""
...
@staticmethod
def mostRecentAvailableConfigVersion(startingPath:str) -> str:
def mostRecentAvailableConfigVersion(startingPath: str, /) -> str:
"""
mostRecentAvailableConfigVersion(startingPath) -> str
Given a base path that is expected to contain versioned subdirectories, locate the
directory name (*not* the path, only the final component, the version string itself)
corresponding to the most recent version of the software, up to and including the current
running version, but NOT exceeding it -- any *later* version whose directories exist
in the path is ignored. See also mostRecentConfigFromBase().
Args:
startingPath: The path to check.
Returns:
Most recent available dir name (not path).
"""
...
@staticmethod
def mostRecentConfigFromBase(startingPath: str) -> str:
def mostRecentConfigFromBase(startingPath: str, /) -> str:
"""
mostRecentConfigFromBase(startingPath) -> str
Given a base path that is expected to contained versioned subdirectories, locate the
directory corresponding to the most recent version of the software, up to and including
the current version, but NOT exceeding it. Returns the complete path, not just the final
component. See also mostRecentAvailableConfigVersion().
Args:
startingPath: The base path to check.
Returns:
Most recent available full path (not just dir name).
"""
...
@staticmethod
def migrateConfig(oldPath: str, newPath: str) -> None:
def migrateConfig(oldPath: str, newPath: str, /) -> None:
"""
migrateConfig(oldPath, newPath) -> None
A utility method to copy all files and directories from oldPath to newPath, handling the
case where newPath might itself be a subdirectory of oldPath (and *not* attempting that
otherwise-recursive copy).
Args:
oldPath: Path from.
newPath: Path to.
"""
...

View File

@@ -31,31 +31,38 @@ namespace App
class Application;
/// Helper class to manager transaction (i.e. undo/redo)
/**
* @brief A helper class to manage transactions (i.e. undo/redo).
*
* An AutoTransaction object is meant to be allocated on the stack and governs
* the transactions in that scope.
*/
class AppExport AutoTransaction
{
public:
/// Private new operator to prevent heap allocation
/// Delete the new operator to prevent heap allocation.
void* operator new(std::size_t) = delete;
public:
/** Constructor
/**
* @brief Construct an auto transaction.
*
* @param name: optional new transaction name on construction
* @param tmpName: if true and a new transaction is setup, the name given is
* @param[in] name: optional new transaction name on construction
* @param[in] tmpName: if true and a new transaction is setup, the name given is
* considered as temporary, and subsequent construction of this class (or
* calling Application::setActiveTransaction()) can override the transaction
* name.
*
* The constructor increments an internal counter
* (Application::_activeTransactionGuard). The counter prevents any new
* active transaction being setup. It also prevents close (i.e. commits) the
* current active transaction until it reaches zero. It does not have any
* effect on aborting transaction, though.
* active transactions being setup. It also prevents to close
* (i.e. commits) the current active transaction until it reaches zero. It
* does not have any effect on aborting transactions though.
*/
AutoTransaction(const char* name = nullptr, bool tmpName = false);
/** Destructor
/**
* @brief Destruct an auto transaction.
*
* This destructor decrease an internal counter
* (Application::_activeTransactionGuard), and will commit any current
@@ -63,15 +70,19 @@ public:
*/
~AutoTransaction();
/** Close or abort the transaction
/**
* @brief Close or abort the transaction.
*
* This function can be used to explicitly close (i.e. commit) the
* transaction, if the current transaction ID matches the one created inside
* the constructor. For aborting, it will abort any current transaction
* the constructor. For aborting, it will abort any current transaction.
*
* @param[in] abort: if true, abort the transaction; otherwise, commit it.
*/
void close(bool abort = false);
/** Enable/Disable any AutoTransaction instance in the current stack
/**
* @brief Enable/Disable any AutoTransaction instance on the current stack.
*
* Once disabled, any empty temporary named transaction is closed. If there
* are non-empty or non-temporary named active transaction, it will not be
@@ -79,6 +90,8 @@ public:
*
* This function may be used in, for example, Gui::Document::setEdit() to
* allow a transaction live past any command scope.
*
* @param[in] enable: if true, enable the AutoTransaction; otherwise, disable it.
*/
static void setEnable(bool enable);
@@ -87,47 +100,55 @@ private:
};
/** Helper class to lock a transaction from being closed or aborted.
/**
* @brief Helper class to lock a transaction from being closed or aborted.
*
* The helper class is used to protect some critical transaction from being
* The helper class is used to protect some critical transactions from being
* closed prematurely, e.g. when deleting some object.
*/
class AppExport TransactionLocker
{
public:
/** Constructor
* @param lock: whether to activate the lock
/**
* @brief Construct a transaction locker.
*
* @param[in] lock: whether to activate the lock
*/
TransactionLocker(bool lock = true);
/** Destructor
* Unlock the transaction is this locker is active
/**
* @brief Destruct a transaction locker.
*
* Unlock the transaction if this locker is active
*/
~TransactionLocker();
/** Activate or deactivate this locker
* @param enable: whether to activate the locker
/**
* @brief Activate or deactivate this locker.
*
* An internal counter is used to support recursive locker. When activated,
* the current active transaction cannot be closed or aborted. But the
* closing call (Application::closeActiveTransaction()) will be remembered,
* and performed when the internal lock counter reaches zero.
*
* @param enable: whether to activate the locker
*/
void activate(bool enable);
/// Check if the locker is active
/// Check if the locker is active.
bool isActive() const
{
return active;
}
/// Check if transaction is being locked
/// Check if transaction is being locked.
static bool isLocked();
friend class Application;
public:
/// Private new operator to prevent heap allocation
/// Delete the new operator to prevent heap allocation.
void* operator new(std::size_t) = delete;
private:

View File

@@ -1,12 +1,16 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from Base.Metadata import export, constmethod
from Base.Persistence import Persistence
from Base.BoundBox import BoundBox as BoundBoxPy
from Base.BoundBox import BoundBox
from Base.Vector import Vector
from Base.Placement import Placement as PlacementPy
from Base.Placement import Placement
from Base.Rotation import Rotation
from Base.Matrix import Matrix
from StringHasher import StringHasher
from typing import Any, Final, List, Dict
from typing import Any, Final
@export(
@@ -15,89 +19,94 @@ from typing import Any, Final, List, Dict
)
class ComplexGeoData(Persistence):
"""
Data.ComplexGeoData class.
Author: Juergen Riegel (Juergen.Riegel@web.de)
Licence: LGPL
UserDocu: Father of all complex geometric data types
Father of all complex geometric data types.
"""
@constmethod
def getElementTypes(self) -> List[str]:
def getElementTypes(self) -> list[str]:
"""
Return a list of element types present in the complex geometric data
Return a list of element types present in the complex geometric data.
"""
...
@constmethod
def countSubElements(self) -> int:
"""
Return the number of elements of a type
Return the number of elements of a type.
"""
...
@constmethod
def getFacesFromSubElement(self) -> Any:
def getFacesFromSubElement(self, ) -> tuple[list[Vector], list[tuple[int, int, int]]]:
"""
Return vertexes and faces from a sub-element
Return vertexes and faces from a sub-element.
"""
...
@constmethod
def getLinesFromSubElement(self) -> Any:
def getLinesFromSubElement(self, ) -> tuple[list[Vector], list[tuple[int, int]]]:
"""
Return vertexes and lines from a sub-element
Return vertexes and lines from a sub-element.
"""
...
@constmethod
def getPoints(self) -> Any:
def getPoints(self) -> tuple[list[Vector], list[Vector]]:
"""
Return a tuple of points and normals with a given accuracy
"""
...
@constmethod
def getLines(self) -> Any:
def getLines(self) -> tuple[list[Vector], list[tuple[int, int]]]:
"""
Return a tuple of points and lines with a given accuracy
"""
...
@constmethod
def getFaces(self) -> Any:
def getFaces(self) -> tuple[list[Vector], list[tuple[int, int, int]]]:
"""
Return a tuple of points and triangles with a given accuracy
"""
...
def applyTranslation(self, translation: Vector) -> None:
def applyTranslation(self, translation: Vector, /) -> None:
"""
Apply an additional translation to the placement
"""
...
def applyRotation(self, rotation: Rotation) -> None:
def applyRotation(self, rotation: Rotation, /) -> None:
"""
Apply an additional rotation to the placement
"""
...
def transformGeometry(self, transformation: Matrix) -> None:
def transformGeometry(self, transformation: Matrix, /) -> None:
"""
Apply a transformation to the underlying geometry
"""
...
def setElementName(self, *, element: str, name: str = None, postfix: str = None, overwrite: bool = False, sid: Any = None) -> None:
def setElementName(
self,
*,
element: str,
name: str = None,
postfix: str = None,
overwrite: bool = False,
sid: Any = None,
) -> None:
"""
setElementName(element,name=None,postfix=None,overwrite=False,sid=None), Set an element name
Set an element name.
element : the original element name, e.g. Edge1, Vertex2
name : the new name for the element, None to remove the mapping
postfix : postfix of the name that will not be hashed
overwrite: if true, it will overwrite exiting name
sid : to hash the name any way you want, provide your own string id(s) in this parameter
Args:
element : the original element name, e.g. Edge1, Vertex2
name : the new name for the element, None to remove the mapping
postfix : postfix of the name that will not be hashed
overwrite: if true, it will overwrite exiting name
sid : to hash the name any way you want, provide your own string id(s) in this parameter
An element can have multiple mapped names. However, a name can only be mapped
to one element
@@ -105,33 +114,33 @@ class ComplexGeoData(Persistence):
...
@constmethod
def getElementName(self, name: str, direction: int = 0) -> Any:
def getElementName(self, name: str, direction: int = 0, /) -> str:
"""
getElementName(name,direction=0) - Return a mapped element name or reverse
Return a mapped element name or reverse.
"""
...
@constmethod
def getElementIndexedName(self, name: str) -> Any:
def getElementIndexedName(self, name: str, /) -> str | tuple[str, list[int]]:
"""
getElementIndexedName(name) - Return the indexed element name
Return the indexed element name.
"""
...
@constmethod
def getElementMappedName(self, name: str) -> Any:
def getElementMappedName(self, name: str, /) -> str | tuple[str, list[int]]:
"""
getElementMappedName(name) - Return the mapped element name
Return the mapped element name
"""
...
BoundBox: Final[BoundBoxPy] = ...
BoundBox: Final[BoundBox] = ...
"""Get the bounding box (BoundBox) of the complex geometric data."""
CenterOfGravity: Final[Vector] = ...
"""Get the center of gravity"""
Placement: PlacementPy = ...
Placement: Placement = ...
"""Get the current transformation of the object as placement"""
Tag: int = 0
@@ -143,10 +152,10 @@ class ComplexGeoData(Persistence):
ElementMapSize: Final[int] = 0
"""Get the current element map size"""
ElementMap: Dict[Any, Any] = {}
ElementMap: dict[str, str] = {}
"""Get/Set a dict of element mapping"""
ElementReverseMap: Final[Dict[Any, Any]] = {}
ElementReverseMap: Final[dict[str, str | list[str]]] = {}
"""Get a dict of element reverse mapping"""
ElementMapVersion: Final[str] = ""

View File

@@ -179,10 +179,15 @@ App::DatumElement* LocalCoordinateSystem::getDatumElement(const char* role) cons
if (featIt != features.end()) {
return static_cast<App::DatumElement*>(*featIt);
}
std::stringstream err;
err << "LocalCoordinateSystem \"" << getFullName() << "\" doesn't contain feature with role \""
<< role << '"';
throw Base::RuntimeError(err.str().c_str());
// During restore, if role lookup fails (e.g. timing issues or fallback to internal name),
// we suppress the error. The default getSubObject will try to resolve it by Internal Name next.
if (!getDocument()->testStatus(App::Document::Restoring)) {
std::stringstream err;
err << "LocalCoordinateSystem \"" << getFullName()
<< "\" doesn't contain feature with role \"" << role << '"';
throw Base::RuntimeError(err.str().c_str());
}
return nullptr;
}
App::Line* LocalCoordinateSystem::getAxis(const char* role) const

View File

@@ -1792,6 +1792,12 @@ bool Document::saveToFile(const char* filename) const
// realpath is canonical filename i.e. without symlink
std::string nativePath = canonical_path(filename);
// check if file is writeable, then block the save if it is not.
Base::FileInfo originalFileInfo(nativePath);
if (originalFileInfo.exists() && !originalFileInfo.isWritable()) {
throw Base::FileException("Unable to save document because file is marked as read-only or write permission is not available.", originalFileInfo);
}
// make a tmp. file where to save the project data first and then rename to
// the actual file name. This may be useful if overwriting an existing file
// fails so that the data of the work up to now isn't lost.
@@ -2086,6 +2092,16 @@ bool Document::afterRestore(const std::vector<DocumentObject*>& objArray, bool c
FC_ERR("Failed to restore " << obj->getFullName() << ": " << e.what());
}
catch (...) {
// If a Python exception occurred, it must be cleared immediately.
// Otherwise, the interpreter remains in a dirty state, causing
// Segfaults later when FreeCAD interacts with Python.
if (PyErr_Occurred()) {
Base::Console().error("Python error during object restore:\n");
PyErr_Print(); // Print the traceback to stderr/Console
PyErr_Clear(); // Reset the interpreter state
}
d->addRecomputeLog("Unknown exception on restore", obj);
FC_ERR("Failed to restore " << obj->getFullName() << ": " << "unknown exception");
}

View File

@@ -1,13 +1,15 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from PropertyContainer import PropertyContainer
from DocumentObject import DocumentObject
from typing import Final, List, Tuple, Sequence
from typing import Final, Sequence
class Document(PropertyContainer):
"""
This is a Document class
Author: Juergen Riegel (FreeCAD@juergen-riegel.net)
Licence: LGPL
This is the Document class.
"""
DependencyGraph: Final[str] = ""
@@ -16,16 +18,16 @@ class Document(PropertyContainer):
ActiveObject: Final[DocumentObject] = None
"""The last created object in this document"""
Objects: Final[List[DocumentObject]] = []
Objects: Final[list[DocumentObject]] = []
"""The list of objects in this document"""
TopologicalSortedObjects: Final[List[DocumentObject]] = []
TopologicalSortedObjects: Final[list[DocumentObject]] = []
"""The list of objects in this document in topological sorted order"""
RootObjects: Final[List[DocumentObject]] = []
RootObjects: Final[list[DocumentObject]] = []
"""The list of root objects in this document"""
RootObjectsIgnoreLinks: Final[List[DocumentObject]] = []
RootObjectsIgnoreLinks: Final[list[DocumentObject]] = []
"""The list of root objects in this document ignoring references from links."""
UndoMode: int = 0
@@ -40,10 +42,10 @@ class Document(PropertyContainer):
RedoCount: Final[int] = 0
"""Number of possible Redos"""
UndoNames: Final[List[str]] = []
UndoNames: Final[list[str]] = []
"""A list of Undo names"""
RedoNames: Final[List[str]] = []
RedoNames: Final[list[str]] = []
"""A List of Redo names"""
Name: Final[str] = ""
@@ -55,10 +57,10 @@ class Document(PropertyContainer):
HasPendingTransaction: Final[bool] = False
"""Check if there is a pending transaction"""
InList: Final[List["Document"]] = []
InList: Final[list[Document]] = []
"""A list of all documents that link to this document."""
OutList: Final[List["Document"]] = []
OutList: Final[list[Document]] = []
"""A list of all documents that this document links to."""
Restoring: Final[bool] = False
@@ -84,25 +86,25 @@ class Document(PropertyContainer):
def save(self) -> None:
"""
Save the document to disk
Save the document to disk.
"""
...
def saveAs(self) -> None:
def saveAs(self, path: str, /) -> None:
"""
Save the document under a new name to disk
Save the document under a new name to disk.
"""
...
def saveCopy(self) -> None:
def saveCopy(self, path: str, /) -> None:
"""
Save a copy of the document under a new name to disk
Save a copy of the document under a new name to disk.
"""
...
def load(self) -> None:
def load(self, path: str, /) -> None:
"""
Load the document from the given path
Load the document from the given path.
"""
...
@@ -131,37 +133,40 @@ class Document(PropertyContainer):
"""
...
def getUniqueObjectName(self, objName: str) -> str:
def getUniqueObjectName(self, objName: str, /) -> str:
"""
getUniqueObjectName(objName) -> objName
Return the same name, or the name made unique, for Example Box -> Box002 if there are conflicting name
already in the document.
ObjName : str
Object name.
Args:
objName: Object name candidate.
Returns:
Unique object name based on objName.
"""
...
def mergeProject(self) -> None:
def mergeProject(self, path: str, /) -> None:
"""
Merges this document with another project file
Merges this document with another project file.
"""
...
def exportGraphviz(self) -> None:
def exportGraphviz(self, path: str = None, /) -> str | None:
"""
Export the dependencies of the objects as graph
Export the dependencies of the objects as graph.
If path is passed, graph is written to it. if not a string is returned.
"""
...
def openTransaction(self, name: str) -> None:
def openTransaction(self, name: str, /) -> None:
"""
openTransaction(name) - Open a new Undo/Redo transaction.
Open a new Undo/Redo transaction.
This function no long creates a new transaction, but calls
FreeCAD.setActiveTransaction(name) instead, which will auto creates a
transaction with the given name when any change happed in any opened document.
transaction with the given name when any change happened in any opened document.
If more than one document is changed, all newly created transactions will have
the same internal ID and will be undo/redo together.
"""
@@ -181,7 +186,6 @@ class Document(PropertyContainer):
def addObject(
self,
*,
type: str,
name: str = None,
objProxy: object = None,
@@ -190,24 +194,22 @@ class Document(PropertyContainer):
viewType: str = None,
) -> DocumentObject:
"""
addObject(type, name=None, objProxy=None, viewProxy=None, attach=False, viewType=None)
Add an object to document.
Add an object to document
type (String): the type of the document object to create.
name (String): the optional name of the new object.
objProxy (Object): the Python binding object to attach to the new document object.
viewProxy (Object): the Python binding object to attach the view provider of this object.
attach (Boolean): if True, then bind the document object first before adding to the document
to allow Python code to override view provider type. Once bound, and before adding to
the document, it will try to call Python binding object's attach(obj) method.
viewType (String): override the view provider type directly, only effective when attach is False.
Args:
type: the type of the document object to create.
name: the optional name of the new object.
objProxy: the Python binding object to attach to the new document object.
viewProxy: the Python binding object to attach the view provider of this object.
attach: if True, then bind the document object first before adding to the document
to allow Python code to override view provider type. Once bound, and before adding to
the document, it will try to call Python binding object's attach(obj) method.
viewType: override the view provider type directly, only effective when attach is False.
"""
...
def addProperty(
self,
*,
type: str,
name: str,
group: str = "",
@@ -216,22 +218,37 @@ class Document(PropertyContainer):
read_only: bool = False,
hidden: bool = False,
locked: bool = False,
) -> "Document":
enum_vals: list[str] | None = None,
) -> Document:
"""
addProperty(type: string, name: string, group="", doc="", attr=0, read_only=False, hidden=False, locked=False) -- Add a generic property.
Add a generic property.
Args:
type: The type of the property to add.
name: The name of the property.
group: The group to which the property belongs. Defaults to "".
doc: The documentation string for the property. Defaults to "".
attr: Attribute flags for the property. Defaults to 0.
read_only: Whether the property is read-only. Defaults to False.
hidden: Whether the property is hidden. Defaults to False.
locked: Whether the property is locked. Defaults to False.
Returns:
The document instance with the added property.
"""
...
def removeProperty(self, string: str) -> None:
def removeProperty(self, name: str, /) -> None:
"""
removeProperty(string) -- Remove a generic property.
Remove a generic property.
Note, you can only remove user-defined properties but not built-in ones.
"""
...
def removeObject(self) -> None:
def removeObject(self, name: str, /) -> None:
"""
Remove an object from the document
Remove an object from the document.
"""
...
@@ -241,34 +258,39 @@ class Document(PropertyContainer):
*,
recursive: bool = False,
return_all: bool = False,
) -> Tuple[DocumentObject, ...]:
) -> tuple[DocumentObject, ...]:
"""
copyObject(object, recursive=False, return_all=False)
Copy an object or objects from another document to this document.
object: can either be a single object or sequence of objects
recursive: if True, also recursively copies internal objects
return_all: if True, returns all copied objects, or else return only the copied
object corresponding to the input objects.
Args:
object: can either be a single object or sequence of objects
recursive: if True, also recursively copies internal objects
return_all: if True, returns all copied objects, or else return only the copied
object corresponding to the input objects.
"""
...
def moveObject(
self, object: DocumentObject, with_dependencies: bool = False
self,
object: DocumentObject,
with_dependencies: bool = False,
/,
) -> DocumentObject:
"""
moveObject(object, bool with_dependencies = False)
Transfers an object from another document to this document.
object: can either a single object or sequence of objects
with_dependencies: if True, all internal dependent objects are copied too.
Args:
object: can either a single object or sequence of objects
with_dependencies: if True, all internal dependent objects are copied too.
"""
...
def importLinks(self, object: DocumentObject = None) -> Tuple[DocumentObject, ...]:
def importLinks(
self,
object: DocumentObject = None,
/,
) -> tuple[DocumentObject, ...]:
"""
importLinks(object|[object...])
Import any externally linked object given a list of objects in
this document. Any link type properties of the input objects
will be automatically reassigned to the imported object
@@ -302,7 +324,7 @@ class Document(PropertyContainer):
"""
...
def setClosable(self, closable: bool) -> None:
def setClosable(self, closable: bool, /) -> None:
"""
Set a flag that allows or forbids to close a document
"""
@@ -314,7 +336,7 @@ class Document(PropertyContainer):
"""
...
def setAutoCreated(self, autoCreated: bool) -> None:
def setAutoCreated(self, autoCreated: bool, /) -> None:
"""
Set a flag that indicates if a document is autoCreated
"""
@@ -326,9 +348,15 @@ class Document(PropertyContainer):
"""
...
def recompute(self, objs: Sequence[DocumentObject] = None) -> int:
def recompute(
self,
objs: Sequence[DocumentObject] = None,
force: bool = False,
check_cycle: bool = False,
/,
) -> int:
"""
recompute(objs=None): Recompute the document and returns the amount of recomputed features
Recompute the document and returns the amount of recomputed features.
"""
...
@@ -350,41 +378,55 @@ class Document(PropertyContainer):
"""
...
def getObject(self, name: str) -> DocumentObject:
def getObject(self, name: str, /) -> DocumentObject:
"""
Return the object with the given name
"""
...
def getObjectsByLabel(self, label: str) -> List[DocumentObject]:
def getObjectsByLabel(self, label: str, /) -> list[DocumentObject]:
"""
Return the objects with the given label name.
NOTE: It's possible that several objects have the same label name.
"""
...
def findObjects(
self, *, Type: str = None, Name: str = None, Label: str = None
) -> List[DocumentObject]:
self,
Type: str = None,
Name: str = None,
Label: str = None,
) -> list[DocumentObject]:
"""
findObjects([Type=string], [Name=string], [Label=string]) -> list
Return a list of objects that match the specified type, name or label.
Name and label support regular expressions. All parameters are optional.
Args:
Type: Type of the feature.
Name: Name
Label: Label
"""
...
def getLinksTo(
self, obj: DocumentObject, options: int = 0, maxCount: int = 0
) -> Tuple[DocumentObject, ...]:
self,
obj: DocumentObject,
options: int = 0,
maxCount: int = 0,
/,
) -> tuple[DocumentObject, ...]:
"""
getLinksTo(obj, options=0, maxCount=0): return objects linked to 'obj'
Return objects linked to 'obj'
options: 1: recursive, 2: check link array. Options can combine.
maxCount: to limit the number of links returned
Args:
options: 1: recursive, 2: check link array. Options can combine.
maxCount: to limit the number of links returned.
"""
...
def supportedTypes(self) -> List[str]:
def supportedTypes(self) -> list[str]:
"""
A list of supported types of objects
"""
@@ -396,12 +438,11 @@ class Document(PropertyContainer):
"""
...
def getDependentDocuments(self, sort: bool = True) -> List[DocumentObject]:
def getDependentDocuments(self, sort: bool = True, /) -> list[DocumentObject]:
"""
getDependentDocuments(sort=True)
Returns a list of documents that this document directly or indirectly links to including itself.
sort: whether to topologically sort the return list
Args:
sort: whether to topologically sort the return list
"""
...

View File

@@ -1,3 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from Base.Metadata import constmethod
from Base.Matrix import Matrix
from Document import Document
@@ -63,7 +67,6 @@ class DocumentObject(ExtensionContainer):
def addProperty(
self,
*,
type: str,
name: str,
group: str = "",
@@ -72,16 +75,16 @@ class DocumentObject(ExtensionContainer):
read_only: bool = False,
hidden: bool = False,
locked: bool = False,
enum_vals: list = []
enum_vals: list = [],
) -> "DocumentObject":
"""
addProperty(type: string, name: string, group="", doc="", attr=0, read_only=False, hidden=False, locked = False, enum_vals=[]) -- Add a generic property.
Add a generic property.
"""
...
def removeProperty(self, string: str) -> None:
def removeProperty(self, string: str, /) -> None:
"""
removeProperty(string) -- Remove a generic property.
Remove a generic property.
Note, you can only remove user-defined properties but not built-in ones.
"""
@@ -111,28 +114,28 @@ class DocumentObject(ExtensionContainer):
"""
...
def setExpression(self, name: str, expression: str) -> None:
def setExpression(self, name: str, expression: str, /) -> None:
"""
Register an expression for a property
"""
...
def clearExpression(self, name: str) -> None:
def clearExpression(self, name: str, /) -> None:
"""
Clear the expression for a property
"""
...
@classmethod
def evalExpression(cls, expression: str) -> Any:
def evalExpression(cls, expression: str, /) -> Any:
"""
Evaluate an expression
"""
...
def recompute(self, recursive: bool = False) -> None:
def recompute(self, recursive: bool = False, /) -> None:
"""
recompute(recursive=False): Recomputes this object
Recomputes this object
"""
...
@@ -155,16 +158,14 @@ class DocumentObject(ExtensionContainer):
def getSubObject(
self,
*,
subname: Union[str, List[str], Tuple[str, ...]],
*,
retType: int = 0,
matrix: Matrix = None,
transform: bool = True,
depth: int = 0,
) -> Any:
"""
getSubObject(subname, retType=0, matrix=None, transform=True, depth=0)
* subname(string|list|tuple): dot separated string or sequence of strings
referencing subobject.
@@ -191,17 +192,15 @@ class DocumentObject(ExtensionContainer):
"""
...
def getSubObjectList(self, subname: str) -> list:
def getSubObjectList(self, subname: str, /) -> list:
"""
getSubObjectList(subname)
Return a list of objects referenced by a given subname including this object
"""
...
def getSubObjects(self, reason: int = 0) -> list:
def getSubObjects(self, reason: int = 0, /) -> list:
"""
getSubObjects(reason=0): Return subname reference of all sub-objects
Return subname reference of all sub-objects
"""
...
@@ -214,7 +213,6 @@ class DocumentObject(ExtensionContainer):
depth: int = 0,
) -> Any:
"""
getLinkedObject(recursive=True, matrix=None, transform=True, depth=0)
Returns the linked object if there is one, or else return itself
* recursive: whether to recursively resolve the links
@@ -229,16 +227,16 @@ class DocumentObject(ExtensionContainer):
"""
...
def setElementVisible(self, element: str, visible: bool) -> int:
def setElementVisible(self, element: str, visible: bool, /) -> int:
"""
setElementVisible(element,visible): Set the visibility of a child element
Set the visibility of a child element
Return -1 if element visibility is not supported, 0 if element not found, 1 if success
"""
...
def isElementVisible(self, element: str) -> int:
def isElementVisible(self, element: str, /) -> int:
"""
isElementVisible(element): Check if a child element is visible
Check if a child element is visible
Return -1 if element visibility is not supported or element not found, 0 if invisible, or else 1
"""
...
@@ -283,9 +281,9 @@ class DocumentObject(ExtensionContainer):
...
@constmethod
def resolve(self, subname: str) -> tuple:
def resolve(self, subname: str, /) -> tuple:
"""
resolve(subname) -- resolve the sub object
resolve the sub object
Returns a tuple (subobj,parent,elementName,subElement), where 'subobj' is the
last object referenced in 'subname', and 'parent' is the direct parent of
@@ -296,9 +294,9 @@ class DocumentObject(ExtensionContainer):
...
@constmethod
def resolveSubElement(self, subname: str, append: bool, type: int) -> tuple:
def resolveSubElement(self, subname: str, append: bool, type: int, /) -> tuple:
"""
resolveSubElement(subname,append,type) -- resolve both new and old style sub element
resolve both new and old style sub element
subname: subname reference containing object hierarchy
append: Whether to append object hierarchy prefix inside subname to returned element name
@@ -308,24 +306,22 @@ class DocumentObject(ExtensionContainer):
"""
...
def adjustRelativeLinks(self, parent: DocumentObject, recursive: bool = True) -> bool:
def adjustRelativeLinks(self, parent: DocumentObject, recursive: bool = True, /) -> bool:
"""
adjustRelativeLinks(parent,recursive=True) -- auto correct potential cyclic dependencies
auto correct potential cyclic dependencies
"""
...
@constmethod
def getElementMapVersion(self, property_name: str) -> str:
def getElementMapVersion(self, property_name: str, /) -> str:
"""
getElementMapVersion(property_name): return element map version of a given geometry property
return element map version of a given geometry property
"""
...
@constmethod
def isAttachedToDocument(self) -> bool:
"""
isAttachedToDocument() -> bool
Return true if the object is part of a document, false otherwise.
"""
...

View File

@@ -1,3 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from Extension import Extension
@@ -7,4 +11,5 @@ class DocumentObjectExtension(Extension):
Author: Stefan Troeger (stefantroeger@gmx.net)
Licence: LGPL
"""
...

View File

@@ -1,3 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from DocumentObject import DocumentObject
@@ -7,4 +11,5 @@ class DocumentObjectGroup(DocumentObject):
Author: Werner Mayer (wmayer@users.sourceforge.net)
Licence: LGPL
"""
...

View File

@@ -1,3 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from typing import Final, Any
from Base.PyObjectBase import PyObjectBase

View File

@@ -1,3 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from Base.Metadata import export, constmethod
from PropertyContainer import PropertyContainer
@@ -13,14 +17,14 @@ class ExtensionContainer(PropertyContainer):
Licence: LGPL
"""
def addExtension(self, identifier: str) -> None:
def addExtension(self, identifier: str, /) -> None:
"""
Adds an extension to the object. Requires the string identifier for the python extension as argument
"""
...
@constmethod
def hasExtension(self, identifier: str) -> bool:
def hasExtension(self, identifier: str, /) -> bool:
"""
Returns if this object has the specified extension
"""

View File

@@ -1,5 +1,9 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from DocumentObject import DocumentObject
from Base import Placement
from Base.Placement import Placement
from typing import Any, Final, Optional
@@ -19,8 +23,6 @@ class GeoFeature(DocumentObject):
def getPaths(self) -> Any:
"""
getPaths()
Returns all possible paths to the root of the document.
Note: Not implemented.
"""
@@ -28,7 +30,6 @@ class GeoFeature(DocumentObject):
def getGlobalPlacement(self) -> Placement:
"""
getGlobalPlacement() -> Base.Placement
Deprecated: This function does not handle Links correctly. Use getGlobalPlacementOf instead.
Returns the placement of the object in the global coordinate space, respecting all stacked
@@ -39,16 +40,18 @@ class GeoFeature(DocumentObject):
...
@staticmethod
def getGlobalPlacementOf(targetObj: Any, rootObj: Any, subname: str) -> Placement:
def getGlobalPlacementOf(targetObj: Any, rootObj: Any, subname: str, /) -> Placement:
"""
getGlobalPlacementOf(targetObj, rootObj, subname) -> Base.Placement
Selection example: obj = "part1" sub = "linkToPart2.LinkToBody.Pad.face1"
Examples:
obj = "part1"
sub = "linkToPart2.LinkToBody.Pad.face1"
Global placement of Pad in this context :
getGlobalPlacementOf(pad, part1, "linkToPart2.LinkToBody.Pad.face1")
Global placement of Pad in this context:
getGlobalPlacementOf(pad, part1, "linkToPart2.LinkToBody.Pad.face1")
Global placement of linkToPart2 in this context :
getGlobalPlacementOf(linkToPart2, part1, "linkToPart2.LinkToBody.Pad.face1")
Global placement of linkToPart2 in this context:
getGlobalPlacementOf(linkToPart2, part1, "linkToPart2.LinkToBody.Pad.face1")
Returns the placement of the object in the global coordinate space, respecting all stacked
relationships.
@@ -57,8 +60,6 @@ class GeoFeature(DocumentObject):
def getPropertyNameOfGeometry(self) -> Optional[str]:
"""
getPropertyNameOfGeometry() -> str or None
Returns the property name of the actual geometry.
For example for a Part feature it returns the value 'Shape', for a mesh feature the value
'Mesh' and so on.
@@ -68,8 +69,6 @@ class GeoFeature(DocumentObject):
def getPropertyOfGeometry(self) -> Optional[Any]:
"""
getPropertyOfGeometry() -> object or None
Returns the property of the actual geometry.
For example for a Part feature it returns its Shape property, for a Mesh feature its
Mesh property and so on.

View File

@@ -1,3 +1,7 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from GroupExtension import GroupExtension
@@ -7,4 +11,5 @@ class GeoFeatureGroupExtension(GroupExtension):
Licence: LGPL
This class handles placeable group of document objects
"""
...

View File

@@ -1,11 +1,13 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from Base.Metadata import export
from DocumentObjectExtension import DocumentObjectExtension
from typing import Any, List
@export(
Include="App/DocumentObjectGroup.h",
)
@export(Include="App/DocumentObjectGroup.h", )
class GroupExtension(DocumentObjectExtension):
"""
Extension class which allows grouping of document objects
@@ -13,37 +15,37 @@ class GroupExtension(DocumentObjectExtension):
Licence: LGPL
"""
def newObject(self, type: str, name: str) -> Any:
def newObject(self, type: str, name: str, /) -> Any:
"""
Create and add an object with given type and name to the group
"""
...
def addObject(self, obj: Any) -> List[Any]:
def addObject(self, obj: Any, /) -> List[Any]:
"""
Add an object to the group. Returns all objects that have been added.
"""
...
def addObjects(self, objects: List[Any]) -> List[Any]:
def addObjects(self, objects: List[Any], /) -> List[Any]:
"""
Adds multiple objects to the group. Expects a list and returns all objects that have been added.
"""
...
def setObjects(self, objects: List[Any]) -> List[Any]:
def setObjects(self, objects: List[Any], /) -> List[Any]:
"""
Sets the objects of the group. Expects a list and returns all objects that are now in the group.
"""
...
def removeObject(self, obj: Any) -> List[Any]:
def removeObject(self, obj: Any, /) -> List[Any]:
"""
Remove an object from the group and returns all objects that have been removed.
"""
...
def removeObjects(self, objects: List[Any]) -> List[Any]:
def removeObjects(self, objects: List[Any], /) -> List[Any]:
"""
Remove multiple objects from the group. Expects a list and returns all objects that have been removed.
"""
@@ -55,30 +57,28 @@ class GroupExtension(DocumentObjectExtension):
"""
...
def getObject(self, name: str) -> Any:
def getObject(self, name: str, /) -> Any:
"""
Return the object with the given name
"""
...
def getObjectsOfType(self, typename: str) -> List[Any]:
def getObjectsOfType(self, typename: str, /) -> List[Any]:
"""
Returns all object in the group of given type
@param typename The Freecad type identifier
"""
...
def hasObject(self, obj: Any, recursive: bool = False) -> bool:
def hasObject(self, obj: Any, recursive: bool = False, /) -> bool:
"""
hasObject(obj, recursive=false)
Checks if the group has a given object
@param obj the object to check for.
@param recursive if true check also if the obj is child of some sub group (default is false).
"""
...
def allowObject(self, obj: Any) -> bool:
def allowObject(self, obj: Any, /) -> bool:
"""
Returns true if obj is allowed in the group extension.
"""

View File

@@ -1,11 +1,13 @@
# SPDX-License-Identifier: LGPL-2.1-or-later
from __future__ import annotations
from Base.Metadata import export
from DocumentObjectExtension import DocumentObjectExtension
from typing import Any, Final, List, Tuple, Optional, Union, overload
@export(
Include="App/Link.h",
)
@export(Include="App/Link.h", )
class LinkBaseExtension(DocumentObjectExtension):
"""
Link extension base class
@@ -16,15 +18,18 @@ class LinkBaseExtension(DocumentObjectExtension):
LinkedChildren: Final[List[Any]] = []
"""Return a flattened (in case grouped by plain group) list of linked children"""
def configLinkProperty(self, **kwargs) -> Any:
def configLinkProperty(self, *args, **kwargs) -> Any:
"""
configLinkProperty(key=val,...): property configuration
configLinkProperty(key,...): property configuration with default name
Examples:
Called with default names:
configLinkProperty(prop1, prop2, ..., propN)
Called with custom names:
configLinkProperty(prop1=val1, prop2=val2, ..., propN=valN)
This methode is here to implement what I called Property Design
This method is here to implement what I called Property Design
Pattern. The extension operates on a predefined set of properties,
but it relies on the extended object to supply the actual property by
calling this methode. You can choose a sub set of functionality of
calling this method. You can choose a sub set of functionality of
this extension by supplying only some of the supported properties.
The 'key' are names used to refer to properties supported by this
@@ -41,48 +46,48 @@ class LinkBaseExtension(DocumentObjectExtension):
"""
...
def getLinkExtProperty(self, name: str) -> Any:
def getLinkExtProperty(self, name: str, /) -> Any:
"""
getLinkExtProperty(name): return the property value by its predefined name
return the property value by its predefined name
"""
...
def getLinkExtPropertyName(self, name: str) -> str:
def getLinkExtPropertyName(self, name: str, /) -> str:
"""
getLinkExtPropertyName(name): lookup the property name by its predefined name
lookup the property name by its predefined name
"""
...
@overload
def getLinkPropertyInfo(self) -> tuple:
def getLinkPropertyInfo(self, /) -> tuple[tuple[str, str, str]]:
...
@overload
def getLinkPropertyInfo(self, index: int) -> tuple:
def getLinkPropertyInfo(self, index: int, /) -> tuple[str, str, str]:
...
@overload
def getLinkPropertyInfo(self, name: str) -> tuple:
def getLinkPropertyInfo(self, name: str, /) -> tuple[str, str]:
...
def getLinkPropertyInfo(self, arg: Any = None) -> tuple:
def getLinkPropertyInfo(self, arg: Any = None, /) -> tuple:
"""
getLinkPropertyInfo(): return a tuple of (name,type,doc) for all supported properties.
getLinkPropertyInfo(index): return (name,type,doc) of a specific property
getLinkPropertyInfo(name): return (type,doc) of a specific property
Overloads:
(): return (name,type,doc) for all supported properties.
(index): return (name,type,doc) of a specific property
(name): return (type,doc) of a specific property
"""
...
def setLink(self, obj: Any, subName: Optional[str] = None, subElements: Optional[Union[str, Tuple[str, ...]]] = None) -> None:
def setLink(
self,
obj: Any,
subName: Optional[str] = None,
subElements: Optional[Union[str, Tuple[str, ...]]] = None,
/,
) -> None:
"""
setLink(obj,subName=None,subElements=None): Set link object.
setLink([obj,...]),
setLink([(obj,subName,subElements),...]),
setLink({index:obj,...}),
setLink({index:(obj,subName,subElements),...}): set link element of a link group.
Called with only obj, set link object, otherwise set link element of a link group.
obj (DocumentObject): the object to link to. If this is None, then the link is cleared
@@ -92,27 +97,23 @@ class LinkBaseExtension(DocumentObjectExtension):
"""
...
def cacheChildLabel(self, enable: bool = True) -> None:
def cacheChildLabel(self, enable: bool = True, /) -> None:
"""
cacheChildLabel(enable=True): enable/disable child label cache
enable/disable child label cache
The cache is not updated on child label change for performance reason. You must
call this function on any child label change
"""
...
def flattenSubname(self, subname: str) -> str:
def flattenSubname(self, subname: str, /) -> str:
"""
flattenSubname(subname) -> string
Return a flattened subname in case it references an object inside a linked plain group
"""
...
def expandSubname(self, subname: str) -> str:
def expandSubname(self, subname: str, /) -> str:
"""
expandSubname(subname) -> string
Return an expanded subname in case it references an object inside a linked plain group
"""
...

Some files were not shown because too many files have changed in this diff Show More