Compare commits
121 Commits
fixSignCommand
...
1.1rc1
| Author | SHA1 | Date | |
|---|---|---|---|
| 53767601ea | |||
| 08dabacfed | |||
| 9b934a45db | |||
| a0a8d7760b | |||
| 4962bbbffe | |||
| 7c6d70ab02 | |||
| 30e94d0b3f | |||
| 70c69bbebc | |||
| 1cd0927a07 | |||
| e5b5fb38a4 | |||
| e55305fb25 | |||
| be96c07587 | |||
| b5dee9832f | |||
| 69f6f9e4b0 | |||
| 9c24507531 | |||
| ada3b7c97e | |||
| bc5b7d66c1 | |||
| 9931e01be7 | |||
| 7176a5c3bb | |||
| ac39784aac | |||
| da432a77af | |||
| e6e0d03034 | |||
| c5387aba66 | |||
| 1d739f9773 | |||
| 7c0c421401 | |||
| 45c5b33db5 | |||
| 501f29a247 | |||
| 57a1ac5812 | |||
| 680ccc0ecf | |||
| 7a7cba5587 | |||
| c90f7d67df | |||
| 402032c230 | |||
| 4ba4d1b848 | |||
| 024e904c9a | |||
| 74879b6738 | |||
| e76fdd82dc | |||
| a7486c6fa9 | |||
| 4a331340fe | |||
| e3d0fe6bb6 | |||
| 8c36c5390a | |||
| 2ed6528d61 | |||
| 1c54777405 | |||
| 4cd7fdc2df | |||
| c637b532f1 | |||
| d5da0227ee | |||
| 3446b933cb | |||
| 6f999f8885 | |||
| 95c547bd5a | |||
| 7aed77aaec | |||
| aa02543896 | |||
| a6da44aa03 | |||
| c8f629b8b4 | |||
| b71344c07b | |||
| 8e73bddec1 | |||
| aa010759d3 | |||
| 383a51075c | |||
| aeb6fca064 | |||
| 32bfee0ada | |||
| cb4d6b9cb7 | |||
| d2a99e83f7 | |||
| 78df9dd937 | |||
| 0b92c77685 | |||
| b73a2cca0d | |||
| 205d024b74 | |||
| fc317b16b0 | |||
| 3b80c25339 | |||
| ccd3e778ff | |||
| 7bf96e2ef0 | |||
| 387c3be2bf | |||
| 2c775840da | |||
| 14d93d24fb | |||
| c7b3353414 | |||
| a469c45a2d | |||
| 4e6bb3f524 | |||
| ba5652e046 | |||
| 40cd4fe772 | |||
| 5f68507f6d | |||
| ae864aedb4 | |||
| d4f2c904f8 | |||
| 2ff62e9727 | |||
| 302fbc6765 | |||
| 456bc178c6 | |||
| 66ed85bb19 | |||
| cd2e26bbea | |||
| 6058a47f58 | |||
| 092ae3f8ea | |||
| 3971ce2bf7 | |||
| 69498be228 | |||
| 08cceec9cf | |||
| 3c6cdb17e0 | |||
| 15de64e30b | |||
| d936ba474c | |||
| b44af1f768 | |||
| f308fb92a3 | |||
| 98a1dd2f14 | |||
| 0887aa188b | |||
| 9a4cedf1a4 | |||
| 894e1d9ae2 | |||
| 37f014998c | |||
| 1d21e56ee1 | |||
| c43b615301 | |||
| 125d3b65c7 | |||
| 9946819e9f | |||
| 06d55d50ce | |||
| 1678981185 | |||
| 4e2d2e3206 | |||
| 3fc8528692 | |||
| 6508023e95 | |||
| ce6d9f361a | |||
| 95fe960485 | |||
| 49d4375f70 | |||
| b0ffa6728c | |||
| 38fccbacf6 | |||
| a8ba9b82a5 | |||
| 647877384a | |||
| a60b4698be | |||
| 1a7fa655f3 | |||
| 6d023afaac | |||
| 4642fecba3 | |||
| 2ae9f89982 | |||
| a6ec30a8fc |
@@ -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
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
jobs:
|
||||
trigger-copr-build:
|
||||
environment: fedora-daily
|
||||
env:
|
||||
copr_token: ${{ secrets.COPR_TOKEN }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -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''
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,7 +53,7 @@ project(FreeCAD)
|
||||
set(PACKAGE_VERSION_MAJOR "1")
|
||||
set(PACKAGE_VERSION_MINOR "1")
|
||||
set(PACKAGE_VERSION_PATCH "0") # number of patch release (e.g. "4" for the 0.18.4 release)
|
||||
set(PACKAGE_VERSION_SUFFIX "dev") # either "dev" for development snapshot or "" (empty string)
|
||||
set(PACKAGE_VERSION_SUFFIX "rc1") # either "dev" for development snapshot or "" (empty string)
|
||||
set(PACKAGE_BUILD_VERSION "0") # used when the same FreeCAD version will be re-released (for example using an updated LibPack)
|
||||
|
||||
set(PACKAGE_VERSION "${PACKAGE_VERSION_MAJOR}.${PACKAGE_VERSION_MINOR}.${PACKAGE_VERSION_PATCH}")
|
||||
|
||||
@@ -71,10 +71,6 @@
|
||||
"type": "BOOL",
|
||||
"value": "ON"
|
||||
},
|
||||
"BUILD_REVERSEENGINEERING": {
|
||||
"type": "BOOL",
|
||||
"value": "OFF"
|
||||
},
|
||||
"ENABLE_DEVELOPER_TESTS": {
|
||||
"type": "BOOL",
|
||||
"value": "ON"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
*.exe
|
||||
*-SHA256.txt
|
||||
MSVCRedist
|
||||
FreeCAD
|
||||
version.nsh
|
||||
@@ -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
|
||||
@@ -1,7 +1,7 @@
|
||||
# Creating a Windows installer for FreeCAD
|
||||
|
||||
These are instructions for building an NSIS-based installer for FreeCAD. They were designed for FreeCAD 0.21 and later,
|
||||
and presume that you have cloned a copy of FreeCAD's source code, and therefore have the directory *src/WindowsInstaller*.
|
||||
and presume that you have cloned a copy of FreeCAD's source code, and therefore have the directory *package/WindowsInstaller*.
|
||||
|
||||
## Install NSIS
|
||||
To set up your system for building an NSIS installer:
|
||||
@@ -15,16 +15,13 @@ To set up your system for building an NSIS installer:
|
||||
4. Download and install the nsProcess plugin from https://nsis.sourceforge.io/NsProcess_plugin -- you will need the version that supports Unicode, so make sure to follow the appropriate instructions on their site to install that one (as of this writing it involves manually copying and renaming the plugin DLL file).
|
||||
|
||||
## Build the installer
|
||||
Next, update the installer settings for the current version of FreeCAD. Starting from the *src/WindowsInstaller* folder in the FreeCAD source tree:
|
||||
1. Set the appropriate version strings for the release you are creating. These are used to construct the filename of the installer, among other things. If you have to upload a new version of the installer for the exact same release of FreeCAD, increment `APP_VERSION BUILD` as needed.
|
||||
```
|
||||
!define APP_VERSION_MAJOR 0
|
||||
!define APP_VERSION_MINOR 21
|
||||
!define APP_VERSION_REVISION 0
|
||||
Next, update the installer settings for the current version of FreeCAD. Starting from the *package/WindowsInstaller* folder in the FreeCAD source tree:
|
||||
1. Set the appropriate version strings for the release you are creating. These are used to construct the filename of the installer, among other things. If you have to upload a new version of the installer for the exact same release of FreeCAD, increment `APP_VERSION BUILD` as needed. The main version numbers are dynamically obtained by calling `freecadcmd.exe`.
|
||||
```nsis
|
||||
!define APP_VERSION_EMERGENCY "RC1"
|
||||
!define APP_VERSION_BUILD 1
|
||||
```
|
||||
2. Within the folder *src/WindowsInstaller*, create a new folder called MSVCRedist and copy the following files from your MSVC installation into it:
|
||||
2. If the installer will be made from a LibPack build create a new folder called MSVCRedist within the folder *package/WindowsInstaller* and copy the following files from your MSVC installation into it:
|
||||
```
|
||||
vcruntime140.dll
|
||||
concrt140.dll
|
||||
@@ -33,28 +30,38 @@ vcamp140.dll
|
||||
vccorlib140.dll
|
||||
vcomp140.dll
|
||||
```
|
||||
3. Open the file *Settings.nsh* with a text editor (both jEdit and Visual Studio Code are good editors for NSIS files). Edit the following paths to correspond to your system: `FILES_FREECAD` corresponds to your installation directory (e.g. `CMAKE_INSTALL_PREFIX` if you self-compiled) and `FILES_DEPS` is the folder you created with the MSVC redistributable files in it.
|
||||
```
|
||||
!define FILES_FREECAD "C:\FreeCAD\Installer\FreeCAD"
|
||||
!define FILES_DEPS "C:\FreeCAD\Installer\MSVCRedist"
|
||||
3. If required open the file *Settings.nsh* with a text editor (both jEdit and Visual Studio Code are good editors for NSIS files). Edit the following paths to correspond to your system: `FILES_FREECAD` corresponds to your installation directory (e.g. `CMAKE_INSTALL_PREFIX` if you self-compiled), `FILES_THUMBS` is the directory where the thumbnailer dll is located and `FILES_DEPS` is the folder you created with the MSVC redistributable files in it. `FILES_DEPS` is not needed if the installer is created from a conda bundle so it is not set by default. These can be set via /D argument for `makensis.exe` or by editing *Settings.nsh*.
|
||||
```nsis
|
||||
!ifndef FILES_FREECAD
|
||||
!define FILES_FREECAD "${__FILEDIR__}\FreeCAD"
|
||||
!endif
|
||||
!ifndef FILES_THUMBS
|
||||
!define FILES_THUMBS "${__FILEDIR__}\thumbnail"
|
||||
!endif
|
||||
|
||||
#!define FILES_DEPS "${__FILEDIR__}\MSVC_Redist"
|
||||
```
|
||||
4. Ensure the FreeCAD files are in place. Here you have two options:
|
||||
* If you are working from an already-compiled version of FreeCAD provided to you by an outside source: in this case, simply ensure that `FILES_FREECAD` is set to the directory containing those files.
|
||||
* If you compiled FreeCAD on your own as described [here](https://wiki.freecad.org/Compile_on_Windows) (and using the Install option outlined there). Then:
|
||||
* Open the file *Settings.nsh* as described in step 3. above and set there</br>
|
||||
`!define FILES_FREECAD` to the folder you specified as `CMAKE_INSTALL_PREFIX`
|
||||
* Copy into that folder the file *Delete.bat* that is part of the installer
|
||||
* Copy into the installation folder the file *Delete.bat* that is part of the installer
|
||||
* open a command line in Windows and change to the folder
|
||||
* run the comamand</br>
|
||||
* run the command</br>
|
||||
`Delete.bat`
|
||||
* (These steps assure that the installer only contains files users need. Moreover it assures that the
|
||||
overall files size is below 2 GB and we can use the most compact compression for the installer.)
|
||||
5. Right-click on the file *FreeCAD-installer.nsi* and choose **Compile NSIS script**
|
||||
to compile the installer.
|
||||
to compile the installer. You can also run from command line to specify some settings
|
||||
```cmd
|
||||
%your_nsis_path%\makensis.exe /D'FILES_FREECAD="D:\some\path\FreeCAD"' /D'FILES_DEPS="${__FILEDIR__}\MSVC_Redist" /D'ExeFile="my-FreeCAD-installer.exe"' FreeCAD-installer.nsi
|
||||
```
|
||||
|
||||
|
||||
NOTE: For test builds of the installer you can turn off the compression. This speeds up
|
||||
NOTE: For test builds of the installer you can turn off compression. This speeds up
|
||||
the build time for the installer a lot but increases its file size. The compression
|
||||
is turned off by uncommenting the line</br>
|
||||
is turned off by commenting the line</br>
|
||||
`SetCompressor /SOLID lzma`</br>
|
||||
in the file *Settings.nsh*.
|
||||
in the file *Settings.nsh* or by defining `FC_TEST_BUILD` in command line
|
||||
```cmd
|
||||
%your_nsis_path%\makensis.exe [OPTIONS] /DFC_TEST_BUILD FreeCAD-installer.nsi
|
||||
```
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
After Width: | Height: | Size: 151 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 116 KiB After Width: | Height: | Size: 116 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 91 KiB After Width: | Height: | Size: 91 KiB |
@@ -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}"
|
||||
|
||||
@@ -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}"
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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}"
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
Name: freecad
|
||||
Epoch: 1
|
||||
Version: 1.1.0~dev
|
||||
Version: 1.1.0~rc1
|
||||
Release: 1%{?dist}
|
||||
|
||||
Summary: A general purpose 3D CAD modeler
|
||||
@@ -44,7 +44,7 @@ Source0: freecad-sources.tar.gz
|
||||
# Utilities
|
||||
BuildRequires: cmake gcc-c++ gettext doxygen swig graphviz gcc-gfortran desktop-file-utils tbb-devel ninja-build strace
|
||||
%if %{with tests}
|
||||
BuildRequires: xorg-x11-server-Xvfb python3-typing-extensions
|
||||
BuildRequires: xorg-x11-server-Xvfb python3-typing-extensions
|
||||
%if %{without bundled_gtest}
|
||||
BuildRequires: gtest-devel gmock-devel
|
||||
%endif
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -8,7 +8,7 @@ preview = ["pixi-build"]
|
||||
|
||||
[package]
|
||||
name = "freecad"
|
||||
version = "1.1.0dev"
|
||||
version = "1.1.0rc1"
|
||||
homepage = "https://freecad.org"
|
||||
repository = "https://github.com/FreeCAD/FreeCAD"
|
||||
description = "FreeCAD"
|
||||
@@ -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"]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
context:
|
||||
version: "1.1.0dev"
|
||||
version: "1.1.0rc1"
|
||||
|
||||
package:
|
||||
name: freecad
|
||||
@@ -145,7 +145,6 @@ requirements:
|
||||
- gmsh
|
||||
- graphviz
|
||||
- ifcopenshell
|
||||
- jinja2
|
||||
- lark
|
||||
- lxml
|
||||
- matplotlib-base
|
||||
|
||||
@@ -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")
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
@@ -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"]}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import sys
|
||||
from os import sys, path
|
||||
from os import path
|
||||
|
||||
# Actual code generation is done in Base/param_utils.py.
|
||||
|
||||
|
||||
@@ -140,16 +140,17 @@ public:
|
||||
bool operator==(const Material& m) const
|
||||
{
|
||||
// clang-format off
|
||||
return _matType == m._matType
|
||||
&& shininess == m.shininess
|
||||
if (!uuid.empty() && uuid == m.uuid) {
|
||||
return true;
|
||||
}
|
||||
return shininess == m.shininess
|
||||
&& transparency == m.transparency
|
||||
&& ambientColor == m.ambientColor
|
||||
&& diffuseColor == m.diffuseColor
|
||||
&& specularColor == m.specularColor
|
||||
&& emissiveColor == m.emissiveColor
|
||||
&& image == m.image
|
||||
&& image == m.imagePath
|
||||
&& uuid == m.uuid;
|
||||
&& imagePath == m.imagePath;
|
||||
// clang-format on
|
||||
}
|
||||
bool operator!=(const Material& m) const
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
#include <Base/Console.h>
|
||||
#include <Base/Exception.h>
|
||||
#include <Base/Interpreter.h>
|
||||
#include <Base/ProgramVersion.h>
|
||||
#include <Base/Reader.h>
|
||||
#include <Base/Writer.h>
|
||||
#include <Base/Quantity.h>
|
||||
@@ -2226,6 +2227,25 @@ unsigned int PropertyBoolList::getMemSize() const
|
||||
// PropertyColor
|
||||
//++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
||||
|
||||
namespace
|
||||
{
|
||||
/// The definition of "alpha" was corrected in FreeCAD 1.1 -- returns true if the reader is working
|
||||
/// on a file that pre-dates that correction.
|
||||
bool readerRequiresAlphaConversion(const Base::XMLReader &reader)
|
||||
{
|
||||
return Base::getVersion(reader.ProgramVersion) < Base::Version::v1_1;
|
||||
}
|
||||
|
||||
/// Given a material, invert the alpha channel of all of its colors.
|
||||
void convertAlphaInMaterial(App::Material& material)
|
||||
{
|
||||
material.ambientColor.a = 1.0F - material.ambientColor.a;
|
||||
material.diffuseColor.a = 1.0F - material.diffuseColor.a;
|
||||
material.specularColor.a = 1.0F - material.specularColor.a;
|
||||
material.emissiveColor.a = 1.0F - material.emissiveColor.a;
|
||||
}
|
||||
}
|
||||
|
||||
TYPESYSTEM_SOURCE(App::PropertyColor, App::Property)
|
||||
|
||||
//**************************************************************************
|
||||
@@ -2367,6 +2387,12 @@ void PropertyColor::Restore(Base::XMLReader& reader)
|
||||
reader.readElement("PropertyColor");
|
||||
// get the value of my Attribute
|
||||
unsigned long rgba = reader.getAttribute<unsigned long>("value");
|
||||
if (readerRequiresAlphaConversion(reader)) {
|
||||
// Convert transparency / alpha value
|
||||
constexpr unsigned long alphaMax = 0xff;
|
||||
unsigned long alpha = alphaMax - (rgba & alphaMax);
|
||||
rgba = rgba - (rgba & alphaMax) + alpha;
|
||||
}
|
||||
setValue(rgba);
|
||||
}
|
||||
|
||||
@@ -2448,6 +2474,8 @@ void PropertyColorList::Restore(Base::XMLReader& reader)
|
||||
// initiate a file read
|
||||
reader.addFile(file.c_str(), this);
|
||||
}
|
||||
|
||||
requiresAlphaConversion = readerRequiresAlphaConversion(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2472,6 +2500,11 @@ void PropertyColorList::RestoreDocFile(Base::Reader& reader)
|
||||
str >> value;
|
||||
it.setPackedValue(value);
|
||||
}
|
||||
if (requiresAlphaConversion) {
|
||||
for (auto& it : values) {
|
||||
it.a = 1.0F - it.a;
|
||||
}
|
||||
}
|
||||
setValues(values);
|
||||
}
|
||||
|
||||
@@ -2702,6 +2735,9 @@ void PropertyMaterial::Restore(Base::XMLReader& reader)
|
||||
_cMat.emissiveColor.setPackedValue(reader.getAttribute<unsigned long>("emissiveColor"));
|
||||
_cMat.shininess = (float)reader.getAttribute<double>("shininess");
|
||||
_cMat.transparency = (float)reader.getAttribute<double>("transparency");
|
||||
if (readerRequiresAlphaConversion(reader)) {
|
||||
convertAlphaInMaterial(_cMat);
|
||||
}
|
||||
if (reader.hasAttribute("image")) {
|
||||
_cMat.image = reader.getAttribute<const char*>("image");
|
||||
}
|
||||
@@ -3247,6 +3283,8 @@ void PropertyMaterialList::Restore(Base::XMLReader& reader)
|
||||
// initiate a file read
|
||||
reader.addFile(file.c_str(), this);
|
||||
}
|
||||
|
||||
requiresAlphaConversion = readerRequiresAlphaConversion(reader);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3330,6 +3368,7 @@ void PropertyMaterialList::RestoreDocFileV0(uint32_t count, Base::Reader& reader
|
||||
str >> valueF;
|
||||
it.transparency = valueF;
|
||||
}
|
||||
convertAlpha(values);
|
||||
setValues(values);
|
||||
}
|
||||
|
||||
@@ -3360,9 +3399,17 @@ void PropertyMaterialList::RestoreDocFileV3(Base::Reader& reader)
|
||||
readString(str, it.imagePath);
|
||||
readString(str, it.uuid);
|
||||
}
|
||||
convertAlpha(values);
|
||||
setValues(values);
|
||||
}
|
||||
|
||||
void PropertyMaterialList::convertAlpha(std::vector<App::Material>& materials) const
|
||||
{
|
||||
if (requiresAlphaConversion) {
|
||||
std::ranges::for_each(materials, convertAlphaInMaterial);
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyMaterialList::readString(Base::InputStream& str, std::string& value)
|
||||
{
|
||||
uint32_t uCt {};
|
||||
|
||||
@@ -1091,6 +1091,9 @@ public:
|
||||
|
||||
protected:
|
||||
Base::Color getPyValue(PyObject* py) const override;
|
||||
|
||||
private:
|
||||
bool requiresAlphaConversion {false}; // In 1.1 the handling of alpha was inverted
|
||||
};
|
||||
|
||||
|
||||
@@ -1294,8 +1297,10 @@ private:
|
||||
void verifyIndex(int index) const;
|
||||
void setMinimumSizeOne();
|
||||
int resizeByOneIfNeeded(int index);
|
||||
void convertAlpha(std::vector<App::Material>& materials) const;
|
||||
|
||||
Format formatVersion {Version_0};
|
||||
bool requiresAlphaConversion {false}; // In 1.1 the handling of alpha was inverted
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -32,20 +32,40 @@ namespace App
|
||||
class Document;
|
||||
class TransactionObject;
|
||||
|
||||
/** Base class of transactional objects
|
||||
/**
|
||||
* @brief Base class of transactional objects.
|
||||
*
|
||||
* A transactional object provides functionality that is inherited by its
|
||||
* children to ensure that these children objects can be targeted by a
|
||||
* transaction.
|
||||
*/
|
||||
class AppExport TransactionalObject: public App::ExtensionContainer
|
||||
{
|
||||
PROPERTY_HEADER_WITH_OVERRIDE(App::TransactionalObject);
|
||||
|
||||
public:
|
||||
/// Constructor
|
||||
TransactionalObject();
|
||||
~TransactionalObject() override;
|
||||
|
||||
/// Check if this object is attached to a document.
|
||||
virtual bool isAttachedToDocument() const;
|
||||
|
||||
/**
|
||||
* @brief Deteach this object from the document.
|
||||
*
|
||||
* @return the name of the document this object was attached to.
|
||||
*/
|
||||
virtual const char* detachFromDocument();
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Notify the document that a property is about to be changed.
|
||||
*
|
||||
* This function is called before the property is changed.
|
||||
*
|
||||
* @param[in,out] doc the document this object is attached to.
|
||||
* @param[in] prop the property that is about to be changed.
|
||||
*/
|
||||
void onBeforeChangeProperty(Document* doc, const Property* prop);
|
||||
};
|
||||
|
||||
|
||||
@@ -52,10 +52,6 @@ Transaction::Transaction(int id)
|
||||
transID = id;
|
||||
}
|
||||
|
||||
/**
|
||||
* A destructor.
|
||||
* A more elaborate description of the destructor.
|
||||
*/
|
||||
Transaction::~Transaction()
|
||||
{
|
||||
auto& index = _Objects.get<0>();
|
||||
@@ -291,16 +287,8 @@ TYPESYSTEM_SOURCE_ABSTRACT(App::TransactionObject, Base::Persistence)
|
||||
//**************************************************************************
|
||||
// Construction/Destruction
|
||||
|
||||
/**
|
||||
* A constructor.
|
||||
* A more elaborate description of the constructor.
|
||||
*/
|
||||
TransactionObject::TransactionObject() = default;
|
||||
|
||||
/**
|
||||
* A destructor.
|
||||
* A more elaborate description of the destructor.
|
||||
*/
|
||||
TransactionObject::~TransactionObject()
|
||||
{
|
||||
for (auto& v : _PropChangeMap) {
|
||||
@@ -344,7 +332,7 @@ void TransactionObject::applyChn(Document& /*Doc*/, TransactionalObject* pcObj,
|
||||
}
|
||||
|
||||
// getPropertyName() is specially coded to be safe even if prop has
|
||||
// been destroies. We must prepare for the case where user removed
|
||||
// been destroyed. We must prepare for the case where user removed
|
||||
// a dynamic property but does not recordered as transaction.
|
||||
auto name = pcObj->getPropertyName(prop);
|
||||
if (!name || (!data.name.empty() && data.name != name)
|
||||
|
||||
@@ -40,52 +40,105 @@ class TransactionObject;
|
||||
class TransactionalObject;
|
||||
|
||||
|
||||
/** Represents a atomic transaction of the document
|
||||
/**
|
||||
* @brief A class that represents an atomic transaction of the document.
|
||||
*
|
||||
* A transaction can contain multiple actions. These actions are represented
|
||||
* by TransactionObject objects. Actions can be on objects: adding one,
|
||||
* removing one, or changing one. It can also be on properties: adding one or
|
||||
* removing one.
|
||||
*/
|
||||
class AppExport Transaction: public Base::Persistence
|
||||
{
|
||||
TYPESYSTEM_HEADER_WITH_OVERRIDE();
|
||||
|
||||
public:
|
||||
/** Construction
|
||||
/**
|
||||
* @brief Construct a transaction.
|
||||
*
|
||||
* @param id: transaction id. If zero, then it will be generated
|
||||
* @param[in] id: The transaction id. If zero, then it will be generated
|
||||
* automatically as a monotonically increasing index across the entire
|
||||
* application. User can pass in a transaction id to group multiple
|
||||
* transactions from different document, so that they can be undo/redo
|
||||
* application. Users can pass in a transaction id to group multiple
|
||||
* transactions from different document, so that they can be undone/redone
|
||||
* together.
|
||||
*/
|
||||
explicit Transaction(int id = 0);
|
||||
/// Construction
|
||||
|
||||
~Transaction() override;
|
||||
|
||||
/// apply the content to the document
|
||||
/**
|
||||
* @brief Apply the content of this transaction to the document.
|
||||
*
|
||||
* @param[in] Doc The document to apply the transaction to.
|
||||
* @param[in] forward If true, apply the transaction; otherwise, undo it.
|
||||
*/
|
||||
void apply(Document& Doc, bool forward);
|
||||
|
||||
// the utf-8 name of the transaction
|
||||
/// The UTF-8 name of the transaction
|
||||
std::string Name;
|
||||
|
||||
unsigned int getMemSize() const override;
|
||||
void Save(Base::Writer& writer) const override;
|
||||
/// This method is used to restore properties from an XML document.
|
||||
void Restore(Base::XMLReader& reader) override;
|
||||
|
||||
/// Return the transaction ID
|
||||
/// Get the transaction ID of this transaction.
|
||||
int getID() const;
|
||||
|
||||
/// Generate a new unique transaction ID
|
||||
/// Generate a new unique transaction ID.
|
||||
static int getNewID();
|
||||
|
||||
/// Get the last transaction ID.
|
||||
static int getLastID();
|
||||
|
||||
/// Returns true if the transaction list is empty; otherwise returns false.
|
||||
/// Check if the transaction list is empty.
|
||||
bool isEmpty() const;
|
||||
/// check if this object is used in a transaction
|
||||
|
||||
/**
|
||||
* @brief Check if this object is used in a transaction.
|
||||
*
|
||||
* @param[in] Obj The object to check.
|
||||
* @return true if the object is used in a transaction; otherwise false.
|
||||
*/
|
||||
bool hasObject(const TransactionalObject* Obj) const;
|
||||
|
||||
/**
|
||||
* @brief Record renaming a property.
|
||||
*
|
||||
* @param[in] Obj The object of which the property to rename.
|
||||
* @param[in] pcProp The property with a new name.
|
||||
* @param[in] oldName The old name of the property.
|
||||
*/
|
||||
void renameProperty(TransactionalObject* Obj, const Property* pcProp, const char* oldName);
|
||||
|
||||
/**
|
||||
* @brief Record adding or removing a property from an object.
|
||||
*
|
||||
* @param[in] Obj The object to add or remove the property from.
|
||||
* @param[in] pcProp The property to add or remove.
|
||||
* @param[in] add If true, add the property; otherwise, remove it.
|
||||
*/
|
||||
void addOrRemoveProperty(TransactionalObject* Obj, const Property* pcProp, bool add);
|
||||
|
||||
/**
|
||||
* @brief Record adding a new object to the transaction.
|
||||
*
|
||||
* @param[in] Obj The object to add.
|
||||
*/
|
||||
void addObjectNew(TransactionalObject* Obj);
|
||||
|
||||
/**
|
||||
* @brief Record removing an object from the transaction.
|
||||
*
|
||||
* @param[in] Obj The object to remove.
|
||||
*/
|
||||
void addObjectDel(const TransactionalObject* Obj);
|
||||
|
||||
/**
|
||||
*@brief Record changing an object in the transaction.
|
||||
*
|
||||
* @param[in] Obj The object to change.
|
||||
* @param[in] Prop The property that is changed.
|
||||
*/
|
||||
void addObjectChange(const TransactionalObject* Obj, const Property* Prop);
|
||||
|
||||
private:
|
||||
@@ -103,41 +156,87 @@ private:
|
||||
_Objects;
|
||||
};
|
||||
|
||||
/** Represents an entry for an object in a Transaction
|
||||
/**
|
||||
* @brief Class that represents an entry for an object in a Transaction.
|
||||
*
|
||||
* This class is used to store the information about the object and its
|
||||
* properties. It should not be confused with the TransactionalObject class
|
||||
* that is a base class that contains functionality for a DocumentObject
|
||||
* regarding transactions.
|
||||
*/
|
||||
class AppExport TransactionObject: public Base::Persistence
|
||||
{
|
||||
TYPESYSTEM_HEADER_WITH_OVERRIDE();
|
||||
|
||||
public:
|
||||
/// Construction
|
||||
TransactionObject();
|
||||
/// Destruction
|
||||
|
||||
~TransactionObject() override;
|
||||
|
||||
virtual void applyNew(Document& Doc, TransactionalObject* pcObj);
|
||||
virtual void applyDel(Document& Doc, TransactionalObject* pcObj);
|
||||
virtual void applyChn(Document& Doc, TransactionalObject* pcObj, bool Forward);
|
||||
/**
|
||||
* @brief Apply the transaction that adds a new object to the document.
|
||||
*
|
||||
* @param[in,out] doc The document to apply the transaction to.
|
||||
* @param[in,out] obj The object that is added in the transaction.
|
||||
*/
|
||||
virtual void applyNew(Document& doc, TransactionalObject* obj);
|
||||
|
||||
void setProperty(const Property* pcProp);
|
||||
void renameProperty(const Property* pcProp, const char* newName);
|
||||
void addOrRemoveProperty(const Property* pcProp, bool add);
|
||||
/**
|
||||
* @brief Apply the transaction that removes an object from the document.
|
||||
*
|
||||
* @param[in,out] doc The document to apply the transaction to.
|
||||
* @param[in,out] obj The object that is removed in the transaction.
|
||||
*/
|
||||
virtual void applyDel(Document& doc, TransactionalObject* obj);
|
||||
|
||||
/**
|
||||
* @brief Apply the transaction that changes an object in the document.
|
||||
*
|
||||
* @param[in,out] doc The document to apply the transaction to.
|
||||
* @param[in,out] obj The object that is changed in the transaction.
|
||||
* @param[in] forward If true, apply the transaction; otherwise, undo it.
|
||||
*/
|
||||
virtual void applyChn(Document& doc, TransactionalObject* obj, bool forward);
|
||||
|
||||
/**
|
||||
* @brief Set the property of the object that is affected by the transaction.
|
||||
*
|
||||
* @param[in] prop The property that is affected by the transaction.
|
||||
*/
|
||||
void setProperty(const Property* prop);
|
||||
|
||||
/**
|
||||
* @brief Rename a property.
|
||||
*
|
||||
* @param[in] pcProp The property with the new name.
|
||||
* @param[in] oldName The old name of the property.
|
||||
*/
|
||||
void renameProperty(const Property* pcProp, const char* oldName);
|
||||
|
||||
/**
|
||||
* @brief Add or remove a property from the object.
|
||||
*
|
||||
* @param[in] prop The property to add or remove.
|
||||
* @param[in] add If true, add the property; otherwise, remove it.
|
||||
*/
|
||||
void addOrRemoveProperty(const Property* prop, bool add);
|
||||
|
||||
unsigned int getMemSize() const override;
|
||||
void Save(Base::Writer& writer) const override;
|
||||
/// This method is used to restore properties from an XML document.
|
||||
void Restore(Base::XMLReader& reader) override;
|
||||
|
||||
friend class Transaction;
|
||||
|
||||
protected:
|
||||
/// The status of the transaction object.
|
||||
enum Status
|
||||
{
|
||||
New,
|
||||
Del,
|
||||
Chn
|
||||
New, ///< A new object is added to the document.
|
||||
Del, ///< An object is deleted from the document.
|
||||
Chn ///< An object is changed in the document.
|
||||
} status {New};
|
||||
|
||||
/// Struct to maintain property information.
|
||||
struct PropData: DynamicProperty::PropData
|
||||
{
|
||||
Base::Type propertyType;
|
||||
@@ -145,34 +244,59 @@ protected:
|
||||
// for property renaming
|
||||
std::string nameOrig;
|
||||
};
|
||||
|
||||
/// A map to maintain the properties of the object.
|
||||
std::unordered_map<int64_t, PropData> _PropChangeMap;
|
||||
|
||||
/// The name of the object in the document.
|
||||
std::string _NameInDocument;
|
||||
};
|
||||
|
||||
/** Represents an entry for a document object in a transaction
|
||||
/**
|
||||
* @brief Class that represents an entry for a document object in a transaction.
|
||||
*/
|
||||
class AppExport TransactionDocumentObject: public TransactionObject
|
||||
{
|
||||
TYPESYSTEM_HEADER_WITH_OVERRIDE();
|
||||
|
||||
public:
|
||||
/// Construction
|
||||
TransactionDocumentObject();
|
||||
/// Destruction
|
||||
|
||||
~TransactionDocumentObject() override;
|
||||
|
||||
void applyNew(Document& Doc, TransactionalObject* pcObj) override;
|
||||
void applyDel(Document& Doc, TransactionalObject* pcObj) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class that represents a factory for creating transaction objects.
|
||||
*
|
||||
* This class is used to create transaction objects for different types of
|
||||
* transactions.
|
||||
*/
|
||||
class AppExport TransactionFactory
|
||||
{
|
||||
public:
|
||||
/// Get the singleton instance of the TransactionFactory.
|
||||
static TransactionFactory& instance();
|
||||
|
||||
/// Destruct the singleton instance of the TransactionFactory.
|
||||
static void destruct();
|
||||
|
||||
/**
|
||||
* @brief Create a transaction object for the given type.
|
||||
*
|
||||
* @param[in] type The type of the transaction object to create.
|
||||
* @return A pointer to the created transaction object.
|
||||
*/
|
||||
TransactionObject* createTransaction(const Base::Type& type) const;
|
||||
|
||||
/**
|
||||
* @brief Add a producer for a transaction object.
|
||||
*
|
||||
* @param[in] type The type id of the transaction object.
|
||||
* @param[in,out] producer The producer to add.
|
||||
*/
|
||||
void addProducer(const Base::Type& type, Base::AbstractProducer* producer);
|
||||
|
||||
private:
|
||||
@@ -183,10 +307,20 @@ private:
|
||||
~TransactionFactory() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Class that represents a producer for creating transaction objects.
|
||||
*
|
||||
* @tparam CLASS The class of the transaction object to produce.
|
||||
*/
|
||||
template<class CLASS>
|
||||
class TransactionProducer: public Base::AbstractProducer
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Construct a transaction producer.
|
||||
*
|
||||
* @param[in] type The type of the transaction object.
|
||||
*/
|
||||
explicit TransactionProducer(const Base::Type& type)
|
||||
{
|
||||
TransactionFactory::instance().addProducer(type, this);
|
||||
@@ -194,9 +328,7 @@ public:
|
||||
|
||||
~TransactionProducer() override = default;
|
||||
|
||||
/**
|
||||
* Creates an instance of the specified transaction object.
|
||||
*/
|
||||
/// Creates an instance of the specified transaction object.
|
||||
void* Produce() const override
|
||||
{
|
||||
return (new CLASS);
|
||||
|
||||
@@ -336,6 +336,44 @@ bool FileInfo::isWritable() const
|
||||
if (fs::is_directory(path)) {
|
||||
return directoryIsWritable(path);
|
||||
}
|
||||
#ifdef FC_OS_WIN32
|
||||
// convert filename from UTF-8 to windows WSTRING
|
||||
std::wstring fileNameWstring = toStdWString();
|
||||
// requires import of <windows.h>
|
||||
DWORD attributes = GetFileAttributes(fileNameWstring.c_str());
|
||||
if (attributes == INVALID_FILE_ATTRIBUTES) {
|
||||
// Log the error?
|
||||
std::clog << "GetFileAttributes failed for file: " << FileName << '\n';
|
||||
// usually indicates some kind of network file issue, so the file is probably not writable
|
||||
return false;
|
||||
}
|
||||
if ((attributes & FILE_ATTRIBUTE_READONLY) != 0) {
|
||||
return false;
|
||||
}
|
||||
// TEST if file is truly writable, because windows ACL does not map well to POSIX perms,
|
||||
// and there are other potential blockers (app or shared file locks, etc)
|
||||
HANDLE hFile = CreateFileW(
|
||||
fileNameWstring.c_str(),
|
||||
GENERIC_WRITE,
|
||||
0, // ----> no sharing: fail if anyone else has it open
|
||||
nullptr,
|
||||
OPEN_EXISTING,
|
||||
FILE_ATTRIBUTE_NORMAL,
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (hFile == INVALID_HANDLE_VALUE) {
|
||||
DWORD err = GetLastError();
|
||||
if (err == ERROR_SHARING_VIOLATION || err == ERROR_LOCK_VIOLATION) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!CloseHandle(hFile)) {
|
||||
std::clog << "CloseHandle failed for file: " << FileName
|
||||
<< " while checking for write access." << '\n';
|
||||
}
|
||||
#endif
|
||||
fs::file_status stat = fs::status(path);
|
||||
fs::perms perms = stat.permissions();
|
||||
return (perms & fs::perms::owner_write) == fs::perms::owner_write;
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2024 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
**************************************************************************/
|
||||
|
||||
#ifndef BASE_PROGRAM_VERSION_H
|
||||
#define BASE_PROGRAM_VERSION_H
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
#include <FCGlobal.h>
|
||||
|
||||
namespace Base
|
||||
{
|
||||
|
||||
enum class Version : std::uint8_t
|
||||
{
|
||||
v0_1x,
|
||||
v0_16,
|
||||
v0_17,
|
||||
v0_18,
|
||||
v0_19,
|
||||
v0_20,
|
||||
v0_21,
|
||||
v0_22,
|
||||
v1_0,
|
||||
v1_1,
|
||||
v1_x,
|
||||
};
|
||||
|
||||
inline Version getVersion(std::string_view str)
|
||||
{
|
||||
// clang-format off
|
||||
struct VersionItem
|
||||
{
|
||||
std::string_view name;
|
||||
Version version;
|
||||
};
|
||||
static const std::initializer_list<VersionItem> items = {
|
||||
{.name="0.16", .version=Version::v0_16},
|
||||
{.name="0.17", .version=Version::v0_17},
|
||||
{.name="0.18", .version=Version::v0_18},
|
||||
{.name="0.19", .version=Version::v0_19},
|
||||
{.name="0.20", .version=Version::v0_20},
|
||||
{.name="0.21", .version=Version::v0_21},
|
||||
{.name="0.22", .version=Version::v0_22},
|
||||
{.name="1.0" , .version=Version::v1_0 },
|
||||
{.name="1.1" , .version=Version::v1_1 },
|
||||
};
|
||||
// clang-format on
|
||||
auto it = std::ranges::find_if(items, [str](const auto& item) {
|
||||
return str.compare(0, item.name.size(), item.name) == 0;
|
||||
});
|
||||
if (it != items.end()) {
|
||||
return it->version;
|
||||
}
|
||||
if (!str.empty() && str[0] == '0') {
|
||||
return Version::v0_1x;
|
||||
}
|
||||
return Version::v1_x;
|
||||
}
|
||||
|
||||
} // namespace Base
|
||||
|
||||
#endif // BASE_PROGRAM_VERSION_H
|
||||
@@ -65,11 +65,7 @@ using namespace std;
|
||||
Base::XMLReader::XMLReader(const char* FileName, std::istream& str)
|
||||
: _File(FileName)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
str.imbue(std::locale::empty());
|
||||
#else
|
||||
str.imbue(std::locale::classic());
|
||||
#endif
|
||||
|
||||
// create the parser
|
||||
parser = XMLReaderFactory::createXMLReader(); // NOLINT
|
||||
|
||||
@@ -318,11 +318,7 @@ void Writer::putNextEntry(const char* file, const char* obj)
|
||||
ZipWriter::ZipWriter(const char* FileName)
|
||||
: ZipStream(FileName)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
ZipStream.imbue(std::locale::empty());
|
||||
#else
|
||||
ZipStream.imbue(std::locale::classic());
|
||||
#endif
|
||||
ZipStream.precision(std::numeric_limits<double>::digits10 + 1);
|
||||
ZipStream.setf(std::ios::fixed, std::ios::floatfield);
|
||||
}
|
||||
@@ -330,11 +326,7 @@ ZipWriter::ZipWriter(const char* FileName)
|
||||
ZipWriter::ZipWriter(std::ostream& os)
|
||||
: ZipStream(os)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
ZipStream.imbue(std::locale::empty());
|
||||
#else
|
||||
ZipStream.imbue(std::locale::classic());
|
||||
#endif
|
||||
ZipStream.precision(std::numeric_limits<double>::digits10 + 1);
|
||||
ZipStream.setf(std::ios::fixed, std::ios::floatfield);
|
||||
}
|
||||
|
||||
@@ -937,7 +937,9 @@ void RecentFilesAction::setFiles(const QStringList& files)
|
||||
for (int index = 0; index < numRecentFiles; index++) {
|
||||
QString numberLabel = numberToLabel(index + 1);
|
||||
QFileInfo fi(files[index]);
|
||||
recentFiles[index]->setText(QStringLiteral("%1 %2").arg(numberLabel).arg(fi.fileName()));
|
||||
QString fileName {fi.fileName()};
|
||||
fileName.replace(QLatin1Char('&'), QStringLiteral("&&"));
|
||||
recentFiles[index]->setText(QStringLiteral("%1 %2").arg(numberLabel, fileName));
|
||||
recentFiles[index]->setStatusTip(tr("Open file %1").arg(files[index]));
|
||||
recentFiles[index]->setToolTip(files[index]); // set the full name that we need later for saving
|
||||
recentFiles[index]->setData(QVariant(index));
|
||||
|
||||
@@ -122,7 +122,7 @@ public:
|
||||
const QString& header,
|
||||
const QString& message,
|
||||
bool check = false,
|
||||
const QString& checkText = QStringLiteral("Don't show me again")
|
||||
const QString& checkText = tr("Don't show me again")
|
||||
);
|
||||
|
||||
/// Same as showMessage above, but it checks the specific preference path and parameter
|
||||
|
||||
@@ -745,7 +745,7 @@ void DlgPropertyLink::onSelectionChanged(const Gui::SelectionChanges& msg)
|
||||
|
||||
ui->treeWidget->scrollToItem(item);
|
||||
if (allowSubObject) {
|
||||
QString element = QString::fromLatin1(msg.Object.getOldElementName().c_str());
|
||||
QString element = QString::fromUtf8(msg.Object.getOldElementName().c_str());
|
||||
if (element.size()) {
|
||||
QStringList list;
|
||||
QString text = item->text(1);
|
||||
@@ -868,7 +868,7 @@ QString DlgPropertyLink::linksToPython(const QList<App::SubObjectT>& links)
|
||||
}
|
||||
|
||||
if (links.size() == 1) {
|
||||
return QString::fromLatin1(links.front().getSubObjectPython(false).c_str());
|
||||
return QString::fromUtf8(links.front().getSubObjectPython(false).c_str());
|
||||
}
|
||||
|
||||
std::ostringstream ss;
|
||||
@@ -891,7 +891,7 @@ QString DlgPropertyLink::linksToPython(const QList<App::SubObjectT>& links)
|
||||
ss << ']';
|
||||
}
|
||||
|
||||
return QString::fromLatin1(ss.str().c_str());
|
||||
return QString::fromUtf8(ss.str().c_str());
|
||||
}
|
||||
|
||||
void DlgPropertyLink::filterObjects()
|
||||
@@ -1114,7 +1114,7 @@ QTreeWidgetItem* DlgPropertyLink::createTypeItem(Base::Type type)
|
||||
item = new QTreeWidgetItem(item);
|
||||
}
|
||||
item->setExpanded(true);
|
||||
item->setText(0, QString::fromLatin1(type.getName()));
|
||||
item->setText(0, QString::fromUtf8(type.getName()));
|
||||
if (type == App::DocumentObject::getClassTypeId()) {
|
||||
item->setFlags(Qt::ItemIsEnabled);
|
||||
}
|
||||
@@ -1129,7 +1129,7 @@ bool DlgPropertyLink::filterType(QTreeWidgetItem* item)
|
||||
auto& pitem = typeItems[proxyType];
|
||||
if (!pitem) {
|
||||
pitem = new QTreeWidgetItem(ui->typeTree);
|
||||
pitem->setText(0, QString::fromLatin1(proxyType));
|
||||
pitem->setText(0, QString::fromUtf8(proxyType));
|
||||
pitem->setIcon(0, item->icon(0));
|
||||
pitem->setData(0, Qt::UserRole, proxyType);
|
||||
}
|
||||
|
||||
@@ -133,8 +133,8 @@ DlgVersionMigrator::DlgVersionMigrator(MainWindow* mw)
|
||||
// NOTE: All rich-text strings are generated programmatically so that translators don't have to
|
||||
// deal with the markup. The two strings in the middle of the dialog are set in the UI file.
|
||||
|
||||
auto programNameString = tr("Welcome to %1 %2.%3\n\n")
|
||||
.arg(programName, QString::number(major), QString::number(minor));
|
||||
auto programNameString
|
||||
= tr("Welcome to %1 %2.%3").arg(programName, QString::number(major), QString::number(minor));
|
||||
auto welcomeString = QStringLiteral("<b>") + programNameString + QStringLiteral("</b>");
|
||||
|
||||
auto calculatingSizeString = QStringLiteral("<b>") + tr("Calculating size…")
|
||||
|
||||
@@ -1414,11 +1414,11 @@ static bool checkCanonicalPath(const std::map<App::Document*, bool>& docs)
|
||||
|
||||
auto docName = [](App::Document* doc) -> QString {
|
||||
if (doc->Label.getStrValue() == doc->getName()) {
|
||||
return QString::fromLatin1(doc->getName());
|
||||
return QString::fromUtf8(doc->getName());
|
||||
}
|
||||
return QStringLiteral("%1 (%2)").arg(
|
||||
QString::fromUtf8(doc->Label.getValue()),
|
||||
QString::fromLatin1(doc->getName())
|
||||
QString::fromUtf8(doc->getName())
|
||||
);
|
||||
};
|
||||
int count = 0;
|
||||
@@ -1823,6 +1823,7 @@ void Document::RestoreDocFile(Base::Reader& reader)
|
||||
localreader->readElement("Document");
|
||||
long scheme = localreader->getAttribute<long>("SchemaVersion");
|
||||
localreader->DocumentSchema = scheme;
|
||||
localreader->ProgramVersion = d->_pcDocument->getProgramVersion();
|
||||
|
||||
bool hasExpansion = localreader->hasAttribute("HasExpansion");
|
||||
if (hasExpansion) {
|
||||
|
||||
@@ -71,13 +71,12 @@ namespace sp = std::placeholders;
|
||||
// taken from the script doctools.py
|
||||
std::string DocumentRecovery::doctools
|
||||
= "import os,sys,string\n"
|
||||
"import xml.sax\n"
|
||||
"import xml.sax.handler\n"
|
||||
"import xml.sax.xmlreader\n"
|
||||
"from defusedxml import sax as defused_sax\n"
|
||||
"from xml.sax.handler import ContentHandler\n"
|
||||
"import zipfile\n"
|
||||
"\n"
|
||||
"# SAX handler to parse the Document.xml\n"
|
||||
"class DocumentHandler(xml.sax.handler.ContentHandler):\n"
|
||||
"class DocumentHandler(ContentHandler):\n"
|
||||
" def __init__(self, dirname):\n"
|
||||
" self.files = []\n"
|
||||
" self.dirname = dirname\n"
|
||||
@@ -108,7 +107,7 @@ std::string DocumentRecovery::doctools
|
||||
" for j in dirs:\n"
|
||||
" curpath=curpath+\"/\"+j\n"
|
||||
" os.mkdir(curpath)\n"
|
||||
" output=open(outpath+\"/\"+i,\'wb\')\n"
|
||||
" output=open(outpath+\"/\"+i,'wb')\n"
|
||||
" output.write(data)\n"
|
||||
" output.close()\n"
|
||||
"\n"
|
||||
@@ -118,7 +117,7 @@ std::string DocumentRecovery::doctools
|
||||
" guixml=os.path.join(dirname,\"GuiDocument.xml\")\n"
|
||||
" if os.path.exists(guixml):\n"
|
||||
" files.extend(getFilesList(guixml))\n"
|
||||
" compress=zipfile.ZipFile(outpath,\'w\',zipfile.ZIP_DEFLATED)\n"
|
||||
" compress=zipfile.ZipFile(outpath,'w',zipfile.ZIP_DEFLATED)\n"
|
||||
" for i in files:\n"
|
||||
" dirs=os.path.split(i)\n"
|
||||
" #print i, dirs[-1]\n"
|
||||
@@ -128,7 +127,7 @@ std::string DocumentRecovery::doctools
|
||||
"def getFilesList(filename):\n"
|
||||
" dirname=os.path.dirname(filename)\n"
|
||||
" handler=DocumentHandler(dirname)\n"
|
||||
" parser=xml.sax.make_parser()\n"
|
||||
" parser=defused_sax.make_parser()\n"
|
||||
" parser.setContentHandler(handler)\n"
|
||||
" parser.parse(filename)\n"
|
||||
"\n"
|
||||
|
||||
@@ -413,7 +413,7 @@ public:
|
||||
res = QString::fromUtf8(quote(doc->Label.getStrValue()).c_str());
|
||||
}
|
||||
else {
|
||||
res = QString::fromLatin1(doc->getName());
|
||||
res = QString::fromUtf8(doc->getName());
|
||||
}
|
||||
if (sep) {
|
||||
res += QLatin1Char('#');
|
||||
@@ -1013,7 +1013,7 @@ void ExpressionLineEdit::slotTextChanged(const QString& text)
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, bool isActivated)
|
||||
void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, ActivationMode mode)
|
||||
{
|
||||
int start, end;
|
||||
completer->getPrefixRange(start, end);
|
||||
@@ -1030,7 +1030,7 @@ void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, bool
|
||||
|
||||
// chain completions if we select an entry from the completer drop down
|
||||
// and that entry ends with '.' or '#'
|
||||
if (isActivated) {
|
||||
if (mode == ActivationMode::Activated) {
|
||||
std::string textToComplete = completionPrefix.toUtf8().constData();
|
||||
if (textToComplete.size()
|
||||
&& (*textToComplete.crbegin() == '.' || *textToComplete.crbegin() == '#')) {
|
||||
@@ -1042,12 +1042,12 @@ void ExpressionLineEdit::slotCompleteText(const QString& completionPrefix, bool
|
||||
|
||||
void ExpressionLineEdit::slotCompleteTextHighlighted(const QString& completionPrefix)
|
||||
{
|
||||
slotCompleteText(completionPrefix, false);
|
||||
slotCompleteText(completionPrefix, ActivationMode::Highlighted);
|
||||
}
|
||||
|
||||
void ExpressionLineEdit::slotCompleteTextSelected(const QString& completionPrefix)
|
||||
{
|
||||
slotCompleteText(completionPrefix, true);
|
||||
slotCompleteText(completionPrefix, ActivationMode::Activated);
|
||||
}
|
||||
|
||||
|
||||
@@ -1116,13 +1116,13 @@ void ExpressionTextEdit::setDocumentObject(const App::DocumentObject* currentDoc
|
||||
completer,
|
||||
qOverload<const QString&>(&QCompleter::activated),
|
||||
this,
|
||||
&ExpressionTextEdit::slotCompleteText
|
||||
&ExpressionTextEdit::slotCompleteTextSelected
|
||||
);
|
||||
connect(
|
||||
completer,
|
||||
qOverload<const QString&>(&QCompleter::highlighted),
|
||||
this,
|
||||
&ExpressionTextEdit::slotCompleteText
|
||||
&ExpressionTextEdit::slotCompleteTextHighlighted
|
||||
);
|
||||
connect(this, &ExpressionTextEdit::textChanged2, completer, &ExpressionCompleter::slotUpdate);
|
||||
connect(
|
||||
@@ -1155,7 +1155,7 @@ void ExpressionTextEdit::slotTextChanged()
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionTextEdit::slotCompleteText(const QString& completionPrefix)
|
||||
void ExpressionTextEdit::slotCompleteText(const QString& completionPrefix, ActivationMode mode)
|
||||
{
|
||||
QTextCursor cursor = textCursor();
|
||||
int start, end;
|
||||
@@ -1165,17 +1165,31 @@ void ExpressionTextEdit::slotCompleteText(const QString& completionPrefix)
|
||||
cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, end - pos);
|
||||
}
|
||||
cursor.movePosition(QTextCursor::PreviousCharacter, QTextCursor::KeepAnchor, end - start);
|
||||
|
||||
Base::FlagToggler<bool> flag(block, false);
|
||||
cursor.insertText(completionPrefix);
|
||||
completer->updatePrefixEnd(cursor.positionInBlock());
|
||||
|
||||
std::string textToComplete = completionPrefix.toUtf8().constData();
|
||||
if (textToComplete.size()
|
||||
&& (*textToComplete.crbegin() == '.' || *textToComplete.crbegin() == '#')) {
|
||||
completer->slotUpdate(cursor.block().text(), cursor.positionInBlock());
|
||||
// chain completions only when activated (Enter/Click), not when highlighted (arrow keys)
|
||||
if (mode == ActivationMode::Activated) {
|
||||
std::string textToComplete = completionPrefix.toUtf8().constData();
|
||||
if (!textToComplete.empty()
|
||||
&& (*textToComplete.crbegin() == '.' || *textToComplete.crbegin() == '#')) {
|
||||
completer->slotUpdate(cursor.block().text(), cursor.positionInBlock());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionTextEdit::slotCompleteTextHighlighted(const QString& completionPrefix)
|
||||
{
|
||||
slotCompleteText(completionPrefix, ActivationMode::Highlighted);
|
||||
}
|
||||
|
||||
void ExpressionTextEdit::slotCompleteTextSelected(const QString& completionPrefix)
|
||||
{
|
||||
slotCompleteText(completionPrefix, ActivationMode::Activated);
|
||||
}
|
||||
|
||||
void ExpressionTextEdit::keyPressEvent(QKeyEvent* e)
|
||||
{
|
||||
Base::FlagToggler<bool> flag(block, true);
|
||||
@@ -1208,9 +1222,8 @@ void ExpressionTextEdit::keyPressEvent(QKeyEvent* e)
|
||||
if (!completer->popup()->currentIndex().isValid()) {
|
||||
completer->popup()->setCurrentIndex(completer->popup()->model()->index(0, 0));
|
||||
}
|
||||
// insert completion
|
||||
completer->setCurrentRow(completer->popup()->currentIndex().row());
|
||||
slotCompleteText(completer->currentCompletion());
|
||||
slotCompleteText(completer->currentCompletion(), ActivationMode::Highlighted);
|
||||
|
||||
// refresh completion list
|
||||
completer->setCompletionPrefix(completer->currentCompletion());
|
||||
|
||||