Merge branch 'main' into CAM-Inspect-Fix
This commit is contained in:
@@ -58,7 +58,7 @@ jobs:
|
||||
logdir: /tmp/log/
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
steps:
|
||||
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -54,7 +54,8 @@ jobs:
|
||||
CCACHE_MAXSIZE: 1G
|
||||
CCACHE_COMPRESS: true
|
||||
CCACHE_COMPRESSLEVEL: 5
|
||||
builddir: ${{ github.workspace }}/build/
|
||||
config: debug
|
||||
builddir: ${{ github.workspace }}/build/debug/
|
||||
cacheKey: pixi-${{ matrix.os }}
|
||||
logdir: ${{ github.workspace }}/logs/
|
||||
reportdir: ${{ github.workspace }}/report/
|
||||
@@ -70,7 +71,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -133,11 +134,11 @@ jobs:
|
||||
|
||||
- name: CMake Configure
|
||||
run: |
|
||||
pixi run configure-release
|
||||
pixi run configure-${{ env.config }}
|
||||
|
||||
- name: CMake Build
|
||||
run: |
|
||||
pixi run build
|
||||
pixi run build-${{ env.config }}
|
||||
|
||||
- name: Print ccache statistics after Build
|
||||
run: |
|
||||
@@ -174,7 +175,7 @@ jobs:
|
||||
|
||||
- name: CMake Install
|
||||
run: |
|
||||
pixi run install
|
||||
pixi run install-${{ env.config }}
|
||||
|
||||
- name: FreeCAD CLI tests on install
|
||||
if: runner.os != 'Windows'
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -198,7 +198,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -81,7 +81,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
build_tag: ${{ steps.tag_build.outputs.build_tag }}
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
environment: weekly-build
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@002fdce3c6a235733a90a27c80493a3241e56863 # v2.12.1
|
||||
uses: step-security/harden-runner@6c439dc8bdf85cadbbce9ed30d1c7b959517bc49 # v2.12.2
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -66,6 +66,6 @@ repos:
|
||||
- id: black
|
||||
args: ['--line-length', '100']
|
||||
- repo: https://github.com/pre-commit/mirrors-clang-format
|
||||
rev: 64827eb3528d4dc019b01153e9fb79107241405f # frozen: v20.1.6
|
||||
rev: 6b9072cd80691b1b48d80046d884409fb1d962d1 # frozen: v20.1.7
|
||||
hooks:
|
||||
- id: clang-format
|
||||
|
||||
@@ -121,7 +121,6 @@
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Linux"
|
||||
},
|
||||
"cmakeExecutable": "${sourceDir}/conda/cmake.sh",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": {
|
||||
"type": "STRING",
|
||||
@@ -161,7 +160,6 @@
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Darwin"
|
||||
},
|
||||
"cmakeExecutable": "${sourceDir}/conda/cmake.sh",
|
||||
"cacheVariables": {
|
||||
"CMAKE_IGNORE_PREFIX_PATH": {
|
||||
"type": "STRING",
|
||||
@@ -189,7 +187,6 @@
|
||||
"lhs": "${hostSystemName}",
|
||||
"rhs": "Windows"
|
||||
},
|
||||
"cmakeExecutable": "${sourceDir}/conda/cmake.cmd",
|
||||
"cacheVariables": {
|
||||
"CMAKE_INSTALL_PREFIX": {
|
||||
"type": "FILEPATH",
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
mamba run --live-stream -n freecad cmake %*
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
mamba run --live-stream -n freecad cmake $@
|
||||
@@ -1,9 +0,0 @@
|
||||
name: freecad
|
||||
channels:
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- conda-forge/noarch::conda-libmamba-solver==24.11.1
|
||||
- conda-devenv
|
||||
- mamba
|
||||
- python==3.12.*
|
||||
- zstd
|
||||
@@ -1,9 +0,0 @@
|
||||
name: freecad
|
||||
channels:
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- conda-forge/noarch::conda-libmamba-solver==24.11.1
|
||||
- conda-devenv
|
||||
- mamba
|
||||
- python==3.11.*
|
||||
- zstd==1.5.6
|
||||
@@ -1,101 +0,0 @@
|
||||
name: freecad
|
||||
channels:
|
||||
- conda-forge
|
||||
- conda-forge/label/pivy_rc
|
||||
dependencies:
|
||||
- conda-forge/noarch::conda-libmamba-solver==24.11.1
|
||||
- libspnav # [linux]
|
||||
- kernel-headers_linux-64 # [linux and x86_64]
|
||||
- libdrm-cos7-x86_64 # [linux and x86_64]
|
||||
- libselinux-cos7-x86_64 # [linux and x86_64]
|
||||
- libsepol-cos7-x86_64 # [linux and x86_64]
|
||||
- libx11-common-cos7-x86_64 # [linux and x86_64]
|
||||
- libx11-cos7-x86_64 # [linux and x86_64]
|
||||
- libxau-cos7-x86_64 # [linux and x86_64]
|
||||
- libxcb-cos7-x86_64 # [linux and x86_64]
|
||||
- libxdamage-cos7-x86_64 # [linux and x86_64]
|
||||
- libxext-cos7-x86_64 # [linux and x86_64]
|
||||
- libxfixes-cos7-x86_64 # [linux and x86_64]
|
||||
- libxi-cos7-x86_64 # [linux and x86_64]
|
||||
- libxi-devel-cos7-x86_64 # [linux and x86_64]
|
||||
- libxxf86vm-cos7-x86_64 # [linux and x86_64]
|
||||
- mesa-dri-drivers-cos7-x86_64 # [linux and x86_64]
|
||||
- mesa-libegl-cos7-x86_64 # [linux and x86_64]
|
||||
- mesa-libegl-devel-cos7-x86_64 # [linux and x86_64]
|
||||
- mesa-libgl-cos7-x86_64 # [linux and x86_64]
|
||||
- mesa-libgl-devel-cos7-x86_64 # [linux and x86_64]
|
||||
- pixman-cos7-x86_64 # [linux and x86_64]
|
||||
- sysroot_linux-64 # [linux and x86_64]
|
||||
- xorg-x11-server-common-cos7-x86_64 # [linux and x86_64]
|
||||
- xorg-x11-server-xvfb-cos7-x86_64 # [linux and x86_64]
|
||||
- kernel-headers_linux-aarch64 # [linux and aarch64]
|
||||
- libdrm-cos7-aarch64 # [linux and aarch64]
|
||||
- libglvnd-cos7-aarch64 # [linux and aarch64]
|
||||
- libglvnd-glx-cos7-aarch64 # [linux and aarch64]
|
||||
- libselinux-cos7-aarch64 # [linux and aarch64]
|
||||
- libsepol-cos7-aarch64 # [linux and aarch64]
|
||||
- libx11-common-cos7-aarch64 # [linux and aarch64]
|
||||
- libx11-cos7-aarch64 # [linux and aarch64]
|
||||
- libxau-cos7-aarch64 # [linux and aarch64]
|
||||
- libxcb-cos7-aarch64 # [linux and aarch64]
|
||||
- libxdamage-cos7-aarch64 # [linux and aarch64]
|
||||
- libxext-cos7-aarch64 # [linux and aarch64]
|
||||
- libxfixes-cos7-aarch64 # [linux and aarch64]
|
||||
- libxi-cos7-aarch64 # [linux and aarch64]
|
||||
- libxi-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- libxxf86vm-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-dri-drivers-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-khr-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libegl-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libegl-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libgbm-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libgl-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libgl-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libglapi-cos7-aarch64 # [linux and aarch64]
|
||||
- pixman-cos7-aarch64 # [linux and aarch64]
|
||||
- sysroot_linux-aarch64 # [linux and aarch64]
|
||||
- xorg-x11-server-common-cos7-aarch64 # [linux and aarch64]
|
||||
- xorg-x11-server-xvfb-cos7-aarch64 # [linux and aarch64]
|
||||
- sed # [unix]
|
||||
- ccache
|
||||
- clang-format
|
||||
- cmake
|
||||
- coin3d
|
||||
- compilers
|
||||
- conda
|
||||
- conda-devenv
|
||||
- debugpy
|
||||
- doxygen
|
||||
- eigen
|
||||
- fmt
|
||||
- freetype
|
||||
- git
|
||||
- gmsh
|
||||
- graphviz
|
||||
- hdf5
|
||||
- libboost-devel
|
||||
- libcxx
|
||||
- mamba
|
||||
- matplotlib
|
||||
- ninja
|
||||
- numpy
|
||||
- occt
|
||||
- openssl
|
||||
- pcl
|
||||
- pip
|
||||
- conda-forge/label/pivy_rc::pivy
|
||||
- pkg-config
|
||||
- ply
|
||||
- pre-commit
|
||||
- pybind11
|
||||
- pyside6
|
||||
- python==3.12.*
|
||||
- pyyaml
|
||||
- qt6-main
|
||||
- smesh
|
||||
- swig
|
||||
- vtk==9.2.6
|
||||
- xerces-c
|
||||
- yaml-cpp
|
||||
- zlib
|
||||
- zstd
|
||||
@@ -1,101 +0,0 @@
|
||||
name: freecad
|
||||
channels:
|
||||
- conda-forge
|
||||
dependencies:
|
||||
- conda-forge/noarch::conda-libmamba-solver==24.11.1
|
||||
- libspnav # [linux]
|
||||
- kernel-headers_linux-64 # [linux and x86_64]
|
||||
- libdrm-cos6-x86_64 # [linux and x86_64]
|
||||
- libselinux-cos6-x86_64 # [linux and x86_64]
|
||||
- libsepol-cos6-x86_64 # [linux and x86_64]
|
||||
- libx11-common-cos6-x86_64 # [linux and x86_64]
|
||||
- libx11-cos6-x86_64 # [linux and x86_64]
|
||||
- libxau-cos6-x86_64 # [linux and x86_64]
|
||||
- libxcb-cos6-x86_64 # [linux and x86_64]
|
||||
- libxdamage-cos6-x86_64 # [linux and x86_64]
|
||||
- libxext-cos6-x86_64 # [linux and x86_64]
|
||||
- libxfixes-cos6-x86_64 # [linux and x86_64]
|
||||
- libxi-cos6-x86_64 # [linux and x86_64]
|
||||
- libxi-devel-cos6-x86_64 # [linux and x86_64]
|
||||
- libxxf86vm-cos6-x86_64 # [linux and x86_64]
|
||||
- mesa-dri-drivers-cos6-x86_64 # [linux and x86_64]
|
||||
- mesa-dri1-drivers-cos6-x86_64 # [linux and x86_64]
|
||||
- mesa-libegl-cos6-x86_64 # [linux and x86_64]
|
||||
- mesa-libegl-devel-cos6-x86_64 # [linux and x86_64]
|
||||
- mesa-libgl-cos6-x86_64 # [linux and x86_64]
|
||||
- mesa-libgl-devel-cos6-x86_64 # [linux and x86_64]
|
||||
- pixman-cos6-x86_64 # [linux and x86_64]
|
||||
- sysroot_linux-64 # [linux and x86_64]
|
||||
- xorg-x11-server-common-cos6-x86_64 # [linux and x86_64]
|
||||
- xorg-x11-server-xvfb-cos6-x86_64 # [linux and x86_64]
|
||||
- kernel-headers_linux-aarch64 # [linux and aarch64]
|
||||
- libdrm-cos7-aarch64 # [linux and aarch64]
|
||||
- libglvnd-cos7-aarch64 # [linux and aarch64]
|
||||
- libglvnd-glx-cos7-aarch64 # [linux and aarch64]
|
||||
- libselinux-cos7-aarch64 # [linux and aarch64]
|
||||
- libsepol-cos7-aarch64 # [linux and aarch64]
|
||||
- libx11-common-cos7-aarch64 # [linux and aarch64]
|
||||
- libx11-cos7-aarch64 # [linux and aarch64]
|
||||
- libxau-cos7-aarch64 # [linux and aarch64]
|
||||
- libxcb-cos7-aarch64 # [linux and aarch64]
|
||||
- libxdamage-cos7-aarch64 # [linux and aarch64]
|
||||
- libxext-cos7-aarch64 # [linux and aarch64]
|
||||
- libxfixes-cos7-aarch64 # [linux and aarch64]
|
||||
- libxi-cos7-aarch64 # [linux and aarch64]
|
||||
- libxi-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- libxxf86vm-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-dri-drivers-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-khr-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libegl-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libegl-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libgbm-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libgl-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libgl-devel-cos7-aarch64 # [linux and aarch64]
|
||||
- mesa-libglapi-cos7-aarch64 # [linux and aarch64]
|
||||
- pixman-cos7-aarch64 # [linux and aarch64]
|
||||
- sysroot_linux-aarch64 # [linux and aarch64]
|
||||
- xorg-x11-server-common-cos7-aarch64 # [linux and aarch64]
|
||||
- xorg-x11-server-xvfb-cos7-aarch64 # [linux and aarch64]
|
||||
- sed # [unix]
|
||||
- ccache
|
||||
- clang-format
|
||||
- cmake
|
||||
- coin3d
|
||||
- compilers
|
||||
- conda
|
||||
- conda-devenv
|
||||
- debugpy
|
||||
- doxygen
|
||||
- eigen
|
||||
- fmt
|
||||
- freetype
|
||||
- git
|
||||
- gmsh
|
||||
- graphviz
|
||||
- hdf5
|
||||
- libboost-devel
|
||||
- libcxx
|
||||
- mamba
|
||||
- matplotlib
|
||||
- ninja
|
||||
- numpy
|
||||
- occt
|
||||
- openssl
|
||||
- pcl
|
||||
- pip
|
||||
- pivy
|
||||
- pkg-config
|
||||
- ply
|
||||
- pre-commit
|
||||
- pybind11
|
||||
- pyside2
|
||||
- python==3.11.*
|
||||
- pyyaml
|
||||
- qt-main
|
||||
- smesh
|
||||
- swig
|
||||
- vtk==9.2.6
|
||||
- xerces-c
|
||||
- yaml-cpp
|
||||
- zlib
|
||||
- zstd==1.5.6
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# create the conda environment as a subdirectory
|
||||
mamba env create -p .conda/freecad -f conda/conda-env.yaml
|
||||
|
||||
# add the environment subdirectory to the conda configuration
|
||||
conda config --add envs_dirs $CONDA_PREFIX/envs
|
||||
conda config --add envs_dirs $(pwd)/.conda
|
||||
conda config --set env_prompt "({name})"
|
||||
|
||||
# install the FreeCAD dependencies into the environment
|
||||
mamba run --live-stream -n freecad mamba-devenv --no-prune -f conda/environment-qt6.devenv.yml
|
||||
@@ -1,10 +0,0 @@
|
||||
:: create the conda environment as a subdirectory
|
||||
call mamba env create -p .conda/freecad -f conda/conda-env.yaml
|
||||
|
||||
:: add the environment subdirectory to the conda configuration
|
||||
call conda config --add envs_dirs %CONDA_PREFIX%/envs
|
||||
call conda config --add envs_dirs %CD%/.conda
|
||||
call conda config --set env_prompt "({name})"
|
||||
|
||||
:: install the FreeCAD dependencies into the environment
|
||||
call mamba run --live-stream -n freecad mamba-devenv --no-prune -f conda/environment.devenv.yml
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# create the conda environment as a subdirectory
|
||||
mamba env create -p .conda/freecad -f conda/conda-env.yaml
|
||||
|
||||
# add the environment subdirectory to the conda configuration
|
||||
conda config --add envs_dirs $CONDA_PREFIX/envs
|
||||
conda config --add envs_dirs $(pwd)/.conda
|
||||
conda config --set env_prompt "({name})"
|
||||
|
||||
# install the FreeCAD dependencies into the environment
|
||||
mamba run --live-stream -n freecad mamba-devenv --no-prune -f conda/environment.devenv.yml
|
||||
Vendored
+2
-2
@@ -100,7 +100,7 @@
|
||||
},
|
||||
{
|
||||
"name": "Python debugger",
|
||||
"type": "python",
|
||||
"type": "debugpy",
|
||||
"request": "attach",
|
||||
"preLaunchTask": "WaitForDebugpy",
|
||||
"redirectOutput": true,
|
||||
@@ -131,4 +131,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,35 +150,41 @@ freecad-stubs = "*"
|
||||
|
||||
## Qt 6 Configuration Presets
|
||||
[target.linux-64.tasks]
|
||||
configure = { cmd = [ "cmake", "-B", "build", "--preset", "conda-linux-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-debug = { cmd = [ "cmake", "-B", "build", "--preset", "conda-linux-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-release = { cmd = [ "cmake", "-B", "build", "--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", "-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="" }}
|
||||
|
||||
[target.linux-aarch64.tasks]
|
||||
configure = { cmd = [ "cmake", "-B", "build", "--preset", "conda-linux-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on= ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-debug = { cmd = [ "cmake", "-B", "build", "--preset", "conda-linux-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on= ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-release = { cmd = [ "cmake", "-B", "build", "--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", "-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="" }}
|
||||
|
||||
[target.osx-64.tasks]
|
||||
configure = { cmd = [ "cmake", "-B", "build", "--preset", "conda-macos-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-debug = { cmd = [ "cmake", "-B", "build", "--preset", "conda-macos-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-release = { cmd = [ "cmake", "-B", "build", "--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", "-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="" }}
|
||||
|
||||
[target.osx-arm64.tasks]
|
||||
configure = { cmd = [ "cmake", "-B", "build", "--preset", "conda-macos-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-debug = { cmd = [ "cmake", "-B", "build", "--preset", "conda-macos-debug", "-DBUILD_REVERSEENGINEERING=OFF" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-release = { cmd = [ "cmake", "-B", "build", "--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", "-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="" }}
|
||||
|
||||
[target.win-64.tasks]
|
||||
configure = { cmd = [ "cmake", "-B", "build", "--preset", "conda-windows-debug", "-DBUILD_REVERSEENGINEERING=OFF", "-DCMAKE_GENERATOR_PLATFORM=", "-DCMAKE_GENERATOR_TOOLSET=" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
configure-debug = { cmd = [ "cmake", "-B", "build", "--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", "-B", "build", "--preset", "conda-windows-release", "-DBUILD_REVERSEENGINEERING=OFF", "-DCMAKE_GENERATOR_PLATFORM=", "-DCMAKE_GENERATOR_TOOLSET=" ], depends-on = ["initialize"], env={ CFLAGS="", CXXFLAGS="", DEBUG_CFLAGS="", DEBUG_CXXFLAGS="" }}
|
||||
freecad = { cmd = [ ".pixi/envs/default/Library/bin/FreeCAD.exe" ], depends-on = ["install"]}
|
||||
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="" }}
|
||||
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"]}
|
||||
|
||||
## Tasks
|
||||
[tasks]
|
||||
initialize = { cmd = ["git", "submodule", "update", "--init", "--recursive"]}
|
||||
build = { cmd = [ "cmake", "--build", "build" ] }
|
||||
install = { cmd = [ "cmake", "--install", "build" ] }
|
||||
test = { cmd = [ "ctest", "--test-dir", "build" ] }
|
||||
freecad = "build/bin/FreeCAD"
|
||||
# redirect to debug by default
|
||||
configure = [{ task = "configure-debug" }]
|
||||
build = [{ task = "build-debug" }]
|
||||
install = [{ task = "install-debug" }]
|
||||
test = [{ task = "test-debug" }]
|
||||
freecad = [{ task = "freecad-debug" }]
|
||||
|
||||
build-debug = { cmd = ["cmake", "--build", "build/debug"]}
|
||||
build-release = { cmd = ["cmake", "--build", "build/release"]}
|
||||
install-debug = { cmd = ["cmake", "--install", "build/debug"]}
|
||||
install-release = { cmd = ["cmake", "--install", "build/release"]}
|
||||
test-debug = { cmd = ["ctest", "--test-dir", "build/debug"]}
|
||||
test-release = { cmd = ["ctest", "--test-dir", "build/release"]}
|
||||
freecad-debug = "build/debug/bin/FreeCAD"
|
||||
freecad-release = "build/release/bin/FreeCAD"
|
||||
|
||||
@@ -356,6 +356,33 @@ struct BaseExport ZipTools
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Helper struct to define inline overloads for the visitor pattern in std::visit.
|
||||
*
|
||||
* It uses type deduction to infer the type from the expression and creates a dedicated type that
|
||||
* essentially is callable using any overload supplied.
|
||||
*
|
||||
* @code
|
||||
* using Base::Overloads;
|
||||
*
|
||||
* const auto visitor = Overloads
|
||||
* {
|
||||
* [](int i){ std::print("int = {}\n", i); },
|
||||
* [](std::string_view s){ std::println("string = “{}”", s); },
|
||||
* [](const Base&){ std::println("base"); },
|
||||
* };
|
||||
* @endcode
|
||||
*
|
||||
* @see https://en.cppreference.com/w/cpp/utility/variant/visit
|
||||
*
|
||||
* @tparam Ts Types for functions that will be used for overloads
|
||||
*/
|
||||
template<class... Ts>
|
||||
struct Overloads: Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
};
|
||||
|
||||
} // namespace Base
|
||||
|
||||
#endif // SRC_BASE_TOOLS_H_
|
||||
|
||||
+53
-28
@@ -137,9 +137,11 @@
|
||||
#include "WorkbenchManipulator.h"
|
||||
#include "WidgetFactory.h"
|
||||
#include "3Dconnexion/navlib/NavlibInterface.h"
|
||||
#include "Inventor/SoFCPlacementIndicatorKit.h"
|
||||
#include "QtWidgets.h"
|
||||
|
||||
#include <Inventor/SoFCPlacementIndicatorKit.h>
|
||||
#include <OverlayManager.h>
|
||||
#include <Base/ServiceProvider.h>
|
||||
|
||||
#ifdef BUILD_TRACY_FRAME_PROFILER
|
||||
#include <tracy/Tracy.hpp>
|
||||
@@ -208,6 +210,8 @@ struct ApplicationP
|
||||
|
||||
// Create the Theme Manager
|
||||
prefPackManager = new PreferencePackManager();
|
||||
// Create the Style Parameter Manager
|
||||
styleParameterManager = new StyleParameters::ParameterManager();
|
||||
}
|
||||
|
||||
~ApplicationP()
|
||||
@@ -221,8 +225,11 @@ struct ApplicationP
|
||||
/// Active document
|
||||
Gui::Document* activeDocument {nullptr};
|
||||
Gui::Document* editDocument {nullptr};
|
||||
|
||||
MacroManager* macroMngr;
|
||||
PreferencePackManager* prefPackManager;
|
||||
StyleParameters::ParameterManager* styleParameterManager;
|
||||
|
||||
/// List of all registered views
|
||||
std::list<Gui::BaseView*> passive;
|
||||
bool isClosing {false};
|
||||
@@ -372,6 +379,31 @@ struct PyMethodDef FreeCADGui_methods[] = {
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
void Application::initStyleParameterManager()
|
||||
{
|
||||
Base::registerServiceImplementation<StyleParameters::ParameterSource>(
|
||||
new StyleParameters::BuiltInParameterSource({.name = QT_TR_NOOP("Built-in Parameters")}));
|
||||
|
||||
Base::registerServiceImplementation<StyleParameters::ParameterSource>(
|
||||
new StyleParameters::UserParameterSource(
|
||||
App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Themes/Tokens"),
|
||||
{.name = QT_TR_NOOP("Theme Parameters"),
|
||||
.options = StyleParameters::ParameterSourceOption::UserEditable}));
|
||||
|
||||
Base::registerServiceImplementation<StyleParameters::ParameterSource>(
|
||||
new StyleParameters::UserParameterSource(
|
||||
App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Themes/UserTokens"),
|
||||
{.name = QT_TR_NOOP("User Parameters"),
|
||||
.options = StyleParameters::ParameterSource::UserEditable}));
|
||||
|
||||
for (auto* source : Base::provideServiceImplementations<StyleParameters::ParameterSource>()) {
|
||||
d->styleParameterManager->addSource(source);
|
||||
}
|
||||
|
||||
Base::registerServiceImplementation(d->styleParameterManager);
|
||||
}
|
||||
// clang-format off
|
||||
Application::Application(bool GUIenabled)
|
||||
{
|
||||
@@ -576,6 +608,8 @@ Application::Application(bool GUIenabled)
|
||||
|
||||
d = new ApplicationP(GUIenabled);
|
||||
|
||||
initStyleParameterManager();
|
||||
|
||||
// global access
|
||||
Instance = this;
|
||||
|
||||
@@ -1955,6 +1989,11 @@ Gui::PreferencePackManager* Application::prefPackManager()
|
||||
return d->prefPackManager;
|
||||
}
|
||||
|
||||
Gui::StyleParameters::ParameterManager* Application::styleParameterManager()
|
||||
{
|
||||
return d->styleParameterManager;
|
||||
}
|
||||
|
||||
|
||||
//**************************************************************************
|
||||
// Init, Destruct and singleton
|
||||
@@ -2314,7 +2353,7 @@ void Application::runApplication()
|
||||
setenv("COIN_EGL", "1", 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Make sure that we use '.' as decimal point. See also
|
||||
// http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=559846
|
||||
// and issue #0002891
|
||||
@@ -2498,36 +2537,22 @@ void Application::setStyleSheet(const QString& qssFile, bool tiledBackground)
|
||||
}
|
||||
}
|
||||
|
||||
QString Application::replaceVariablesInQss(QString qssText)
|
||||
void Application::reloadStyleSheet()
|
||||
{
|
||||
// First we fetch the colors from preferences,
|
||||
ParameterGrp::handle hGrp =
|
||||
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Themes");
|
||||
unsigned long longAccentColor1 = hGrp->GetUnsigned("ThemeAccentColor1", 0);
|
||||
unsigned long longAccentColor2 = hGrp->GetUnsigned("ThemeAccentColor2", 0);
|
||||
unsigned long longAccentColor3 = hGrp->GetUnsigned("ThemeAccentColor3", 0);
|
||||
const MainWindow* mw = getMainWindow();
|
||||
|
||||
// convert them to hex.
|
||||
// Note: the ulong contains alpha channels so 8 hex characters when we need 6 here.
|
||||
QString accentColor1 = QStringLiteral("#%1")
|
||||
.arg(longAccentColor1, 8, 16, QLatin1Char('0'))
|
||||
.toUpper()
|
||||
.mid(0, 7);
|
||||
QString accentColor2 = QStringLiteral("#%1")
|
||||
.arg(longAccentColor2, 8, 16, QLatin1Char('0'))
|
||||
.toUpper()
|
||||
.mid(0, 7);
|
||||
QString accentColor3 = QStringLiteral("#%1")
|
||||
.arg(longAccentColor3, 8, 16, QLatin1Char('0'))
|
||||
.toUpper()
|
||||
.mid(0, 7);
|
||||
const QString qssFile = mw->property("fc_currentStyleSheet").toString();
|
||||
const bool tiledBackground = mw->property("fc_tiledBackground").toBool();
|
||||
|
||||
qssText = qssText.replace(QStringLiteral("@ThemeAccentColor1"), accentColor1);
|
||||
qssText = qssText.replace(QStringLiteral("@ThemeAccentColor2"), accentColor2);
|
||||
qssText = qssText.replace(QStringLiteral("@ThemeAccentColor3"), accentColor3);
|
||||
d->styleParameterManager->reload();
|
||||
|
||||
// Base::Console().warning("%s\n", qssText.toStdString());
|
||||
return qssText;
|
||||
setStyleSheet(qssFile, tiledBackground);
|
||||
OverlayManager::instance()->refresh(nullptr, true);
|
||||
}
|
||||
|
||||
QString Application::replaceVariablesInQss(const QString& qssText)
|
||||
{
|
||||
return QString::fromStdString(d->styleParameterManager->replacePlaceholders(qssText.toStdString()));
|
||||
}
|
||||
|
||||
void Application::checkForDeprecatedSettings()
|
||||
|
||||
@@ -30,6 +30,8 @@
|
||||
|
||||
#include <App/Application.h>
|
||||
|
||||
#include "StyleParameters/ParameterManager.h"
|
||||
|
||||
class QCloseEvent;
|
||||
class SoNode;
|
||||
class NavlibInterface;
|
||||
@@ -63,6 +65,9 @@ public:
|
||||
/// destruction
|
||||
~Application();
|
||||
|
||||
/// Initializes default configuration for Style Parameter Manager
|
||||
void initStyleParameterManager();
|
||||
|
||||
/** @name methods for support of files */
|
||||
//@{
|
||||
/// open a file
|
||||
@@ -221,7 +226,8 @@ public:
|
||||
//@{
|
||||
/// Activate a stylesheet
|
||||
void setStyleSheet(const QString& qssFile, bool tiledBackground);
|
||||
QString replaceVariablesInQss(QString qssText);
|
||||
void reloadStyleSheet();
|
||||
QString replaceVariablesInQss(const QString& qssText);
|
||||
//@}
|
||||
|
||||
/** @name User Commands */
|
||||
@@ -235,6 +241,7 @@ public:
|
||||
//@}
|
||||
|
||||
Gui::PreferencePackManager* prefPackManager();
|
||||
Gui::StyleParameters::ParameterManager* styleParameterManager();
|
||||
|
||||
/** @name Init, Destruct an Access methods */
|
||||
//@{
|
||||
|
||||
@@ -55,6 +55,7 @@ include_directories(
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Quarter
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/PreferencePages
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/Selection
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/StyleParameters
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/..
|
||||
${CMAKE_CURRENT_BINARY_DIR}/..
|
||||
@@ -401,6 +402,7 @@ SET(Gui_UIC_SRCS
|
||||
Dialogs/DlgProjectUtility.ui
|
||||
Dialogs/DlgPropertyLink.ui
|
||||
Dialogs/DlgRevertToBackupConfig.ui
|
||||
Dialogs/DlgThemeEditor.ui
|
||||
PreferencePages/DlgSettings3DView.ui
|
||||
PreferencePages/DlgSettingsCacheDirectory.ui
|
||||
Dialogs/DlgSettingsColorGradient.ui
|
||||
@@ -516,6 +518,7 @@ SET(Dialog_CPP_SRCS
|
||||
Dialogs/DlgPropertyLink.cpp
|
||||
Dialogs/DlgRevertToBackupConfigImp.cpp
|
||||
Dialogs/DlgExpressionInput.cpp
|
||||
Dialogs/DlgThemeEditor.cpp
|
||||
TaskDlgRelocation.cpp
|
||||
Dialogs/DlgCheckableMessageBox.cpp
|
||||
TaskTransform.cpp
|
||||
@@ -558,6 +561,7 @@ SET(Dialog_HPP_SRCS
|
||||
Dialogs/DlgRevertToBackupConfigImp.h
|
||||
Dialogs/DlgCheckableMessageBox.h
|
||||
Dialogs/DlgExpressionInput.h
|
||||
Dialogs/DlgThemeEditor.h
|
||||
TaskDlgRelocation.h
|
||||
TaskTransform.h
|
||||
Dialogs/DlgUndoRedo.h
|
||||
@@ -1326,6 +1330,8 @@ SET(FreeCADGui_CPP_SRCS
|
||||
StartupProcess.cpp
|
||||
TransactionObject.cpp
|
||||
ToolHandler.cpp
|
||||
StyleParameters/Parser.cpp
|
||||
StyleParameters/ParameterManager.cpp
|
||||
)
|
||||
SET(FreeCADGui_SRCS
|
||||
Application.h
|
||||
@@ -1368,6 +1374,8 @@ SET(FreeCADGui_SRCS
|
||||
StartupProcess.h
|
||||
TransactionObject.h
|
||||
ToolHandler.h
|
||||
StyleParameters/Parser.h
|
||||
StyleParameters/ParameterManager.h
|
||||
)
|
||||
|
||||
SET(FreeCADGui_SRCS
|
||||
|
||||
+39
-93
@@ -574,8 +574,8 @@ StdCmdFreeCADDonation::StdCmdFreeCADDonation()
|
||||
:Command("Std_FreeCADDonation")
|
||||
{
|
||||
sGroup = "Help";
|
||||
sMenuText = QT_TR_NOOP("Support FreeCA&D");
|
||||
sToolTipText = QT_TR_NOOP("Support FreeCAD development");
|
||||
sMenuText = QT_TR_NOOP("Donate to FreeCA&D");
|
||||
sToolTipText = QT_TR_NOOP("Support the FreeCAD development");
|
||||
sWhatsThis = "Std_FreeCADDonation";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "internet-web-browser";
|
||||
@@ -586,11 +586,45 @@ void StdCmdFreeCADDonation::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites");
|
||||
std::string url = hURLGrp->GetASCII("DonatePage", "https://wiki.freecad.org/Donate");
|
||||
std::string url = hURLGrp->GetASCII("DonatePage", "https://www.freecad.org/sponsor");
|
||||
hURLGrp->SetASCII("DonatePage", url.c_str());
|
||||
OpenURLInBrowser(url.c_str());
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_FreeDevHandbook
|
||||
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD(StdCmdDevHandbook)
|
||||
|
||||
StdCmdDevHandbook::StdCmdDevHandbook()
|
||||
|
||||
: Command("Std_DevHandbook")
|
||||
{
|
||||
sGroup = "Help";
|
||||
sMenuText = QT_TR_NOOP("Developers Handbook");
|
||||
|
||||
sToolTipText = QT_TR_NOOP("Handbook about FreeCAD development");
|
||||
|
||||
sWhatsThis = "Std_DevHandbook";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "internet-web-browser";
|
||||
eType = 0;
|
||||
}
|
||||
|
||||
void StdCmdDevHandbook::activated(int iMsg)
|
||||
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/Websites");
|
||||
std::string url = hURLGrp->GetASCII("DevHandbook", "https://freecad.github.io/DevelopersHandbook/");
|
||||
|
||||
hURLGrp->SetASCII("DevHandbook", url.c_str());
|
||||
OpenURLInBrowser(url.c_str());
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_FreeCADWebsite
|
||||
//===========================================================================
|
||||
@@ -647,34 +681,6 @@ void StdCmdFreeCADUserHub::activated(int iMsg)
|
||||
OpenURLInBrowser(url.c_str());
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_FreeCADPowerUserHub
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD(StdCmdFreeCADPowerUserHub)
|
||||
|
||||
StdCmdFreeCADPowerUserHub::StdCmdFreeCADPowerUserHub()
|
||||
:Command("Std_FreeCADPowerUserHub")
|
||||
{
|
||||
sGroup = "Help";
|
||||
sMenuText = QT_TR_NOOP("&Python Scripting Documentation");
|
||||
sToolTipText = QT_TR_NOOP("Opens the Python Scripting documentation");
|
||||
sWhatsThis = "Std_FreeCADPowerUserHub";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "applications-python";
|
||||
eType = 0;
|
||||
}
|
||||
|
||||
void StdCmdFreeCADPowerUserHub::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
std::string defaulturl = QCoreApplication::translate(this->className(),"https://wiki.freecad.org/Power_users_hub").toStdString();
|
||||
ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites");
|
||||
std::string url = hURLGrp->GetASCII("PowerUsers", defaulturl.c_str());
|
||||
hURLGrp->SetASCII("PowerUsers", url.c_str());
|
||||
OpenURLInBrowser(url.c_str());
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_FreeCADForum
|
||||
//===========================================================================
|
||||
@@ -703,59 +709,6 @@ void StdCmdFreeCADForum::activated(int iMsg)
|
||||
OpenURLInBrowser(url.c_str());
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_FreeCADFAQ
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD(StdCmdFreeCADFAQ)
|
||||
|
||||
StdCmdFreeCADFAQ::StdCmdFreeCADFAQ()
|
||||
:Command("Std_FreeCADFAQ")
|
||||
{
|
||||
sGroup = "Help";
|
||||
sMenuText = QT_TR_NOOP("FreeCAD FA&Q");
|
||||
sToolTipText = QT_TR_NOOP("Opens the Frequently Asked Questions");
|
||||
sWhatsThis = "Std_FreeCADFAQ";
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "internet-web-browser";
|
||||
eType = 0;
|
||||
}
|
||||
|
||||
void StdCmdFreeCADFAQ::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
std::string defaulturl = QCoreApplication::translate(this->className(),"https://wiki.freecad.org/Frequently_asked_questions").toStdString();
|
||||
ParameterGrp::handle hURLGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Websites");
|
||||
std::string url = hURLGrp->GetASCII("FAQ", defaulturl.c_str());
|
||||
hURLGrp->SetASCII("FAQ", url.c_str());
|
||||
OpenURLInBrowser(url.c_str());
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// Std_PythonWebsite
|
||||
//===========================================================================
|
||||
|
||||
DEF_STD_CMD(StdCmdPythonWebsite)
|
||||
|
||||
StdCmdPythonWebsite::StdCmdPythonWebsite()
|
||||
:Command("Std_PythonWebsite")
|
||||
{
|
||||
sGroup = "Help";
|
||||
sMenuText = QT_TR_NOOP("Python Website");
|
||||
sToolTipText = QT_TR_NOOP("The official Python website");
|
||||
sWhatsThis = "Std_PythonWebsite";
|
||||
sStatusTip = QT_TR_NOOP("Python Website");
|
||||
sPixmap = "applications-python";
|
||||
eType = 0;
|
||||
}
|
||||
|
||||
void StdCmdPythonWebsite::activated(int iMsg)
|
||||
{
|
||||
Q_UNUSED(iMsg);
|
||||
OpenURLInBrowser("https://www.python.org");
|
||||
}
|
||||
|
||||
|
||||
//===========================================================================
|
||||
// Std_ReportBug
|
||||
//===========================================================================
|
||||
@@ -964,12 +917,7 @@ StdCmdReloadStyleSheet::StdCmdReloadStyleSheet()
|
||||
|
||||
void StdCmdReloadStyleSheet::activated(int )
|
||||
{
|
||||
auto mw = getMainWindow();
|
||||
|
||||
auto qssFile = mw->property("fc_currentStyleSheet").toString();
|
||||
auto tiledBackground = mw->property("fc_tiledBackground").toBool();
|
||||
|
||||
Gui::Application::Instance->setStyleSheet(qssFile, tiledBackground);
|
||||
Application::Instance->reloadStyleSheet();
|
||||
}
|
||||
|
||||
namespace Gui {
|
||||
@@ -996,15 +944,13 @@ void CreateStdCommands()
|
||||
rcCmdMgr.addCommand(new StdCmdFreeCADWebsite());
|
||||
rcCmdMgr.addCommand(new StdCmdFreeCADDonation());
|
||||
rcCmdMgr.addCommand(new StdCmdFreeCADUserHub());
|
||||
rcCmdMgr.addCommand(new StdCmdFreeCADPowerUserHub());
|
||||
rcCmdMgr.addCommand(new StdCmdFreeCADForum());
|
||||
rcCmdMgr.addCommand(new StdCmdFreeCADFAQ());
|
||||
rcCmdMgr.addCommand(new StdCmdPythonWebsite());
|
||||
rcCmdMgr.addCommand(new StdCmdReportBug());
|
||||
rcCmdMgr.addCommand(new StdCmdTextDocument());
|
||||
rcCmdMgr.addCommand(new StdCmdUnitsCalculator());
|
||||
rcCmdMgr.addCommand(new StdCmdUserEditMode());
|
||||
rcCmdMgr.addCommand(new StdCmdReloadStyleSheet());
|
||||
rcCmdMgr.addCommand(new StdCmdDevHandbook());
|
||||
//rcCmdMgr.addCommand(new StdCmdDownloadOnlineHelp());
|
||||
//rcCmdMgr.addCommand(new StdCmdDescription());
|
||||
}
|
||||
|
||||
@@ -0,0 +1,764 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <[email protected]> *
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include "Tools.h"
|
||||
#include "Application.h"
|
||||
#include "OverlayManager.h"
|
||||
#include "DlgThemeEditor.h"
|
||||
#include "ui_DlgThemeEditor.h"
|
||||
#include "BitmapFactory.h"
|
||||
|
||||
#include <Base/ServiceProvider.h>
|
||||
#include <Base/Tools.h>
|
||||
|
||||
#ifndef _PreComp_
|
||||
# include <ranges>
|
||||
# include <QImageReader>
|
||||
# include <QPainter>
|
||||
# include <QStyledItemDelegate>
|
||||
# include <QTimer>
|
||||
#endif
|
||||
|
||||
QPixmap colorPreview(const QColor& color)
|
||||
{
|
||||
constexpr qsizetype size = 16;
|
||||
|
||||
QPixmap preview = Gui::BitmapFactory().empty({ size, size });
|
||||
|
||||
QPainter painter(&preview);
|
||||
painter.setRenderHint(QPainter::Antialiasing);
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setBrush(color);
|
||||
painter.drawEllipse(QRect { 0, 0, size, size });
|
||||
|
||||
return preview;
|
||||
}
|
||||
|
||||
QString typeOfTokenValue(const Gui::StyleParameters::Value& value)
|
||||
{
|
||||
// clang-format off
|
||||
return std::visit(
|
||||
Base::Overloads {
|
||||
[](const std::string&) {
|
||||
return QWidget::tr("Generic");
|
||||
},
|
||||
[](const Gui::StyleParameters::Length&) {
|
||||
return QWidget::tr("Length");
|
||||
},
|
||||
[](const QColor&) {
|
||||
return QWidget::tr("Color");
|
||||
}
|
||||
},
|
||||
value
|
||||
);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
namespace Gui
|
||||
{
|
||||
struct StyleParametersModel::Item
|
||||
{
|
||||
Item() = default;
|
||||
virtual ~Item() = default;
|
||||
|
||||
FC_DEFAULT_COPY_MOVE(Item);
|
||||
|
||||
virtual bool isHeader() const = 0;
|
||||
};
|
||||
|
||||
struct StyleParametersModel::GroupItem: Item
|
||||
{
|
||||
explicit GroupItem(QString title, ParameterSource* source)
|
||||
: title(std::move(title))
|
||||
, canAddNewParameters(source && source->metadata.options.testFlag(UserEditable))
|
||||
, source(source)
|
||||
{}
|
||||
|
||||
bool isHeader() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
QString title;
|
||||
bool canAddNewParameters {false};
|
||||
ParameterSource* source;
|
||||
std::set<std::string> deleted {};
|
||||
};
|
||||
|
||||
struct StyleParametersModel::ParameterItem: Item
|
||||
{
|
||||
ParameterItem(QString name, StyleParameters::Parameter token)
|
||||
: name(std::move(name))
|
||||
, token(std::move(token))
|
||||
{}
|
||||
|
||||
bool isHeader() const override
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
QString name;
|
||||
StyleParameters::Parameter token;
|
||||
QFlags<Qt::ItemFlag> flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
||||
};
|
||||
|
||||
class StyleParametersModel::Node
|
||||
{
|
||||
public:
|
||||
explicit Node(std::unique_ptr<Item> data, Node* parent = nullptr)
|
||||
: _parent(parent)
|
||||
, _data(std::move(data))
|
||||
{}
|
||||
|
||||
void appendChild(std::unique_ptr<Node> child)
|
||||
{
|
||||
child->_parent = this;
|
||||
_children.push_back(std::move(child));
|
||||
}
|
||||
|
||||
void removeChild(const int row)
|
||||
{
|
||||
if (row >= 0 && row < static_cast<int>(_children.size())) {
|
||||
_children.erase(_children.begin() + row);
|
||||
}
|
||||
}
|
||||
|
||||
Node* child(const int row) const
|
||||
{
|
||||
if (row < 0 || row >= static_cast<int>(_children.size())) {
|
||||
if (!_empty) {
|
||||
_empty = std::make_unique<Node>(nullptr, const_cast<Node*>(this));
|
||||
}
|
||||
|
||||
return _empty.get();
|
||||
}
|
||||
|
||||
return _children[row].get();
|
||||
}
|
||||
|
||||
int childCount() const
|
||||
{
|
||||
return static_cast<int>(_children.size());
|
||||
}
|
||||
|
||||
int row() const
|
||||
{
|
||||
if (!_parent) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto& siblings = _parent->_children;
|
||||
for (size_t i = 0; i < siblings.size(); ++i) {
|
||||
if (siblings[i].get() == this) {
|
||||
return static_cast<int>(i);
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
Item* data() const
|
||||
{
|
||||
return _data.get();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
T* data() const
|
||||
{
|
||||
return dynamic_cast<T*>(_data.get());
|
||||
}
|
||||
|
||||
Node* parent() const
|
||||
{
|
||||
return _parent;
|
||||
}
|
||||
|
||||
private:
|
||||
Node* _parent;
|
||||
std::vector<std::unique_ptr<Node>> _children {};
|
||||
|
||||
mutable std::unique_ptr<Node> _empty {};
|
||||
std::unique_ptr<Item> _data {};
|
||||
};
|
||||
|
||||
class DlgThemeEditor::Delegate: public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
QRegularExpression validNameRegExp { QStringLiteral("^[A-Z][a-zA-Z0-9]*$") };
|
||||
QRegularExpressionValidator* nameValidator;
|
||||
|
||||
public:
|
||||
explicit Delegate(QObject* parent = nullptr)
|
||||
: QStyledItemDelegate(parent)
|
||||
, nameValidator(new QRegularExpressionValidator(validNameRegExp, this))
|
||||
{}
|
||||
|
||||
QWidget* createEditor(QWidget* parent,
|
||||
[[maybe_unused]] const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const override
|
||||
{
|
||||
auto model = dynamic_cast<const StyleParametersModel*>(index.model());
|
||||
if (!model) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (model->item<StyleParametersModel::ParameterItem>(index)
|
||||
&& index.column() == StyleParametersModel::ParameterExpression) {
|
||||
return new QLineEdit(parent);
|
||||
}
|
||||
|
||||
if (index.column() == StyleParametersModel::ParameterName) {
|
||||
auto editor = new QLineEdit(parent);
|
||||
editor->setValidator(nameValidator);
|
||||
return editor;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void setEditorData(QWidget* editor, const QModelIndex& index) const override
|
||||
{
|
||||
if (auto* lineEdit = qobject_cast<QLineEdit*>(editor)) {
|
||||
lineEdit->setText(index.data(Qt::DisplayRole).toString());
|
||||
}
|
||||
}
|
||||
|
||||
void setModelData(QWidget* editor,
|
||||
QAbstractItemModel* model,
|
||||
const QModelIndex& index) const override
|
||||
{
|
||||
if (auto* lineEdit = qobject_cast<QLineEdit*>(editor)) {
|
||||
model->setData(index, lineEdit->text(), Qt::EditRole);
|
||||
}
|
||||
}
|
||||
|
||||
void updateEditorGeometry(QWidget* editor,
|
||||
const QStyleOptionViewItem& option,
|
||||
[[maybe_unused]] const QModelIndex& index) const override
|
||||
{
|
||||
editor->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override
|
||||
{
|
||||
constexpr int height = 36;
|
||||
|
||||
QSize base = QStyledItemDelegate::sizeHint(option, index);
|
||||
return {base.width(), std::max(base.height(), height)};
|
||||
}
|
||||
|
||||
void paintAddPlaceholder(QPainter* painter, const QStyleOptionViewItem& option) const
|
||||
{
|
||||
QStyle* style = option.widget ? option.widget->style() : QApplication::style();
|
||||
QRect rect = style->subElementRect(QStyle::SE_ItemViewItemText, &option, option.widget);
|
||||
|
||||
QFont font = option.font;
|
||||
font.setItalic(true);
|
||||
|
||||
painter->setFont(font);
|
||||
painter->drawText(rect, Qt::AlignLeft | Qt::AlignVCenter, tr("New parameter..."));
|
||||
}
|
||||
|
||||
void paint(QPainter* painter,
|
||||
const QStyleOptionViewItem& option,
|
||||
const QModelIndex& index) const override
|
||||
{
|
||||
auto model = dynamic_cast<const StyleParametersModel*>(index.model());
|
||||
|
||||
painter->save();
|
||||
|
||||
QStyleOptionViewItem opt(option);
|
||||
initStyleOption(&opt, index);
|
||||
|
||||
if (model->isAddPlaceholder(index)) {
|
||||
if (index.column() == StyleParametersModel::ParameterName) {
|
||||
paintAddPlaceholder(painter, opt);
|
||||
}
|
||||
}
|
||||
else if (model->item<StyleParametersModel::GroupItem>(index)) {
|
||||
constexpr int headerContrast = 120;
|
||||
|
||||
const bool isLightTheme = option.palette.color(QPalette::Text).lightness() < 128;
|
||||
|
||||
const QColor headerBackgroundColor = QtTools::valueOr(
|
||||
option.widget->property("headerBackgroundColor"),
|
||||
isLightTheme
|
||||
? option.palette.color(QPalette::AlternateBase).darker(headerContrast)
|
||||
: option.palette.color(QPalette::AlternateBase).lighter(headerContrast)
|
||||
);
|
||||
|
||||
painter->fillRect(option.rect, headerBackgroundColor);
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
else {
|
||||
QStyledItemDelegate::paint(painter, option, index);
|
||||
}
|
||||
|
||||
painter->restore();
|
||||
}
|
||||
};
|
||||
|
||||
void TokenTreeView::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
static constexpr auto expressionEditKeys = { Qt::Key_Return, Qt::Key_Enter, Qt::Key_Space };
|
||||
static constexpr auto nameEditKeys = { Qt::Key_F2 };
|
||||
static constexpr auto deleteKeys = { Qt::Key_Delete };
|
||||
|
||||
const auto isCorrectKey = [&event](auto key) { return event->key() == key; };
|
||||
|
||||
if (QModelIndex index = currentIndex(); index.isValid()) {
|
||||
if (std::ranges::any_of(expressionEditKeys, isCorrectKey)) {
|
||||
edit(index.siblingAtColumn(StyleParametersModel::ParameterExpression));
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::ranges::any_of(nameEditKeys, isCorrectKey)) {
|
||||
edit(index.siblingAtColumn(StyleParametersModel::ParameterName));
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::ranges::any_of(deleteKeys, isCorrectKey)) {
|
||||
requestRemove(currentIndex());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
QTreeView::keyPressEvent(event);
|
||||
}
|
||||
|
||||
StyleParametersModel::StyleParametersModel(
|
||||
const std::list<StyleParameters::ParameterSource*>& sources,
|
||||
QObject* parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, ParameterSource({ .name = QT_TR_NOOP("All Theme Editor Parameters") })
|
||||
, sources(sources)
|
||||
, manager(new StyleParameters::ParameterManager())
|
||||
{
|
||||
// The parameter model serves as the source, so the manager can compute all necessary things
|
||||
manager->addSource(this);
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
StyleParametersModel::~StyleParametersModel() = default;
|
||||
|
||||
std::list<StyleParameters::Parameter> StyleParametersModel::all() const
|
||||
{
|
||||
std::map<std::string, StyleParameters::Parameter> result;
|
||||
|
||||
QtTools::walkTreeModel(this, [this, &result](const QModelIndex& index) {
|
||||
if (auto parameterItem = item<ParameterItem>(index)) {
|
||||
if (result.contains(parameterItem->token.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
result[parameterItem->token.name] = parameterItem->token;
|
||||
}
|
||||
});
|
||||
|
||||
const auto values = result | std::ranges::views::values;
|
||||
return std::list<StyleParameters::Parameter>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
std::optional<StyleParameters::Parameter> StyleParametersModel::get(const std::string& name) const
|
||||
{
|
||||
std::optional<StyleParameters::Parameter> result = std::nullopt;
|
||||
|
||||
QtTools::walkTreeModel(this, [this, &name, &result](const QModelIndex& index) {
|
||||
if (auto parameterItem = item<ParameterItem>(index)) {
|
||||
if (parameterItem->token.name == name) {
|
||||
result = parameterItem->token;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void StyleParametersModel::removeItem(const QModelIndex& index)
|
||||
{
|
||||
if (auto parameterItem = item<ParameterItem>(index)) {
|
||||
auto groupItem = item<GroupItem>(index.parent());
|
||||
|
||||
if (!groupItem->source->metadata.options.testFlag(UserEditable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
groupItem->deleted.insert(parameterItem->token.name);
|
||||
|
||||
beginRemoveRows(index.parent(), index.row(), index.row());
|
||||
node(index.parent())->removeChild(index.row());
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
|
||||
void StyleParametersModel::reset()
|
||||
{
|
||||
using enum StyleParameters::ParameterSourceOption;
|
||||
|
||||
beginResetModel();
|
||||
root = std::make_unique<Node>(std::make_unique<GroupItem>(tr("Root"), nullptr));
|
||||
|
||||
for (auto* source : sources) {
|
||||
auto groupNode = std::make_unique<Node>(
|
||||
std::make_unique<GroupItem>(tr(source->metadata.name.c_str()), source));
|
||||
|
||||
for (const auto& parameter : source->all()) {
|
||||
auto item = std::make_unique<Node>(
|
||||
std::make_unique<ParameterItem>(QString::fromStdString(parameter.name), parameter));
|
||||
|
||||
if (source->metadata.options.testFlag(ReadOnly)) {
|
||||
item->data<ParameterItem>()->flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
|
||||
}
|
||||
|
||||
groupNode->appendChild(std::move(item));
|
||||
}
|
||||
|
||||
root->appendChild(std::move(groupNode));
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void StyleParametersModel::flush()
|
||||
{
|
||||
QtTools::walkTreeModel(this, [this](const QModelIndex& index) {
|
||||
if (const auto& groupItem = item<GroupItem>(index)) {
|
||||
for (const auto& parameter : groupItem->deleted) {
|
||||
groupItem->source->remove(parameter);
|
||||
}
|
||||
|
||||
groupItem->deleted.clear();
|
||||
}
|
||||
|
||||
if (const auto& parameterItem = item<ParameterItem>(index)) {
|
||||
const auto& groupItem = item<GroupItem>(index.parent());
|
||||
|
||||
groupItem->source->define(parameterItem->token);
|
||||
}
|
||||
});
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
int StyleParametersModel::rowCount(const QModelIndex& index) const
|
||||
{
|
||||
if (index.column() > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int childCount = node(index)->childCount();
|
||||
|
||||
if (const auto& groupItem = item<GroupItem>(index)) {
|
||||
return childCount + (groupItem->canAddNewParameters ? 1 : 0);
|
||||
}
|
||||
|
||||
return childCount;
|
||||
}
|
||||
|
||||
int StyleParametersModel::columnCount([[maybe_unused]] const QModelIndex& index) const
|
||||
{
|
||||
return ColumnCount;
|
||||
}
|
||||
|
||||
QVariant StyleParametersModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
switch (section) {
|
||||
case ParameterName:
|
||||
return tr("Name");
|
||||
case ParameterExpression:
|
||||
return tr("Expression");
|
||||
case ParameterPreview:
|
||||
return tr("Preview");
|
||||
case ParameterType:
|
||||
return tr("Type");
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant StyleParametersModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (auto parameterItem = item<ParameterItem>(index)) {
|
||||
const auto& [name, token, _] = *parameterItem;
|
||||
const auto& value = manager->resolve(name.toStdString());
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (index.column() == ParameterName) {
|
||||
return name;
|
||||
}
|
||||
if (index.column() == ParameterExpression) {
|
||||
return QString::fromStdString(token.value);
|
||||
}
|
||||
if (index.column() == ParameterType) {
|
||||
return typeOfTokenValue(value);
|
||||
}
|
||||
if (index.column() == ParameterPreview) {
|
||||
return QString::fromStdString(value.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (role == Qt::DecorationRole) {
|
||||
if (index.column() == ParameterPreview && std::holds_alternative<QColor>(value)) {
|
||||
return colorPreview(std::get<QColor>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (auto groupItem = item<GroupItem>(index)) {
|
||||
if (role == Qt::DisplayRole && index.column() == ParameterName) {
|
||||
return groupItem->title;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
bool StyleParametersModel::setData(const QModelIndex& index,
|
||||
const QVariant& value,
|
||||
[[maybe_unused]] int role)
|
||||
{
|
||||
if (auto parameterItem = item<ParameterItem>(index)) {
|
||||
auto groupItem = item<GroupItem>(index.parent());
|
||||
|
||||
if (index.column() == ParameterName) {
|
||||
QString newName = value.toString();
|
||||
|
||||
StyleParameters::Parameter newToken = parameterItem->token;
|
||||
newToken.name = newName.toStdString();
|
||||
|
||||
// there is no rename operation, so we need to mark the previous token as deleted
|
||||
groupItem->deleted.insert(parameterItem->token.name);
|
||||
|
||||
parameterItem->name = newName;
|
||||
parameterItem->token = newToken;
|
||||
}
|
||||
|
||||
if (index.column() == ParameterExpression) {
|
||||
QString newValue = value.toString();
|
||||
|
||||
StyleParameters::Parameter newToken = parameterItem->token;
|
||||
newToken.value = newValue.toStdString();
|
||||
|
||||
parameterItem->token = newToken;
|
||||
}
|
||||
}
|
||||
|
||||
if (isAddPlaceholder(index)) {
|
||||
if (index.column() == ParameterName) {
|
||||
QString newName = value.toString();
|
||||
|
||||
if (newName.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StyleParameters::Parameter token { .name = newName.toStdString(), .value = "" };
|
||||
|
||||
int start = rowCount(index.parent());
|
||||
|
||||
beginInsertRows(index.parent(), start, start + 1);
|
||||
auto item = std::make_unique<Node>(
|
||||
std::make_unique<ParameterItem>(newName, token));
|
||||
node(index.parent())->appendChild(std::move(item));
|
||||
endInsertRows();
|
||||
|
||||
// this must be queued to basically next frame so widget has a chance to update
|
||||
QTimer::singleShot(0, [this, index]() {
|
||||
this->newParameterAdded(index);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
this->manager->reload();
|
||||
|
||||
QtTools::walkTreeModel(this, [this](const QModelIndex& index) {
|
||||
const QModelIndex previewColumnIndex = index.siblingAtColumn(ParameterPreview);
|
||||
|
||||
Q_EMIT dataChanged(previewColumnIndex, previewColumnIndex);
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Qt::ItemFlags StyleParametersModel::flags(const QModelIndex& index) const
|
||||
{
|
||||
if (auto parameterItem = item<ParameterItem>(index)) {
|
||||
if (index.column() == ParameterName || index.column() == ParameterExpression) {
|
||||
return parameterItem->flags | QAbstractItemModel::flags(index);
|
||||
}
|
||||
}
|
||||
|
||||
if (isAddPlaceholder(index)) {
|
||||
if (index.column() == ParameterName) {
|
||||
return Qt::ItemIsEnabled | Qt::ItemIsEditable | QAbstractItemModel::flags(index);
|
||||
}
|
||||
}
|
||||
|
||||
return QAbstractItemModel::flags(index);
|
||||
}
|
||||
|
||||
QModelIndex StyleParametersModel::index(int row, int col, const QModelIndex& parent) const
|
||||
{
|
||||
if (!hasIndex(row, col, parent)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto child = node(parent)->child(row)) {
|
||||
return createIndex(row, col, child);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex StyleParametersModel::parent(const QModelIndex& index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
auto node = static_cast<Node*>(index.internalPointer());
|
||||
auto parent = node->parent();
|
||||
|
||||
if (!parent || parent == root.get()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return createIndex(parent->row(), 0, parent);
|
||||
}
|
||||
|
||||
bool StyleParametersModel::isAddPlaceholder(const QModelIndex& index) const
|
||||
{
|
||||
return item(index) == nullptr;
|
||||
}
|
||||
|
||||
StyleParametersModel::Node* StyleParametersModel::node(const QModelIndex& index) const
|
||||
{
|
||||
return index.isValid() ? static_cast<Node*>(index.internalPointer()) : root.get();
|
||||
}
|
||||
|
||||
StyleParametersModel::Item* StyleParametersModel::item(const QModelIndex& index) const
|
||||
{
|
||||
return node(index)->data();
|
||||
}
|
||||
|
||||
DlgThemeEditor::DlgThemeEditor(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::DlgThemeEditor)
|
||||
, model(std::make_unique<StyleParametersModel>(
|
||||
Base::provideServiceImplementations<StyleParameters::ParameterSource>(),
|
||||
this))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
ui->tokensTreeView->setMouseTracking(true);
|
||||
ui->tokensTreeView->setItemDelegate(new Delegate(ui->tokensTreeView));
|
||||
ui->tokensTreeView->setModel(model.get());
|
||||
|
||||
constexpr int typeColumnWidth = 80;
|
||||
constexpr int nameColumnWidth = 200;
|
||||
|
||||
struct ColumnDefinition // NOLINT(*-pro-type-member-init)
|
||||
{
|
||||
StyleParametersModel::Column column;
|
||||
QHeaderView::ResizeMode mode;
|
||||
qsizetype defaultWidth = 0;
|
||||
};
|
||||
|
||||
static constexpr std::initializer_list<ColumnDefinition> columnSizingDefinitions = {
|
||||
{StyleParametersModel::ParameterName, QHeaderView::ResizeMode::ResizeToContents},
|
||||
{StyleParametersModel::ParameterExpression, QHeaderView::ResizeMode::Stretch},
|
||||
{StyleParametersModel::ParameterPreview, QHeaderView::ResizeMode::Stretch},
|
||||
{StyleParametersModel::ParameterType, QHeaderView::ResizeMode::Fixed, typeColumnWidth},
|
||||
};
|
||||
|
||||
for (const auto& [column, mode, defaultWidth] : columnSizingDefinitions) {
|
||||
ui->tokensTreeView->header()->setSectionResizeMode(column, mode);
|
||||
|
||||
if (defaultWidth > 0) {
|
||||
ui->tokensTreeView->header()->setDefaultSectionSize(defaultWidth);
|
||||
}
|
||||
}
|
||||
|
||||
ui->tokensTreeView->setColumnWidth(StyleParametersModel::ParameterName, nameColumnWidth);
|
||||
ui->tokensTreeView->expandAll();
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, this, &DlgThemeEditor::handleButtonClick);
|
||||
|
||||
connect(ui->tokensTreeView,
|
||||
&TokenTreeView::requestRemove,
|
||||
model.get(),
|
||||
qOverload<const QModelIndex&>(&StyleParametersModel::removeItem));
|
||||
|
||||
connect(model.get(), &StyleParametersModel::modelReset, ui->tokensTreeView, [this] {
|
||||
ui->tokensTreeView->expandAll();
|
||||
});
|
||||
connect(model.get(),
|
||||
&StyleParametersModel::newParameterAdded,
|
||||
this,
|
||||
[this](const QModelIndex& index) {
|
||||
const auto newParameterExpressionIndex =
|
||||
index.siblingAtColumn(StyleParametersModel::ParameterExpression);
|
||||
|
||||
ui->tokensTreeView->scrollTo(newParameterExpressionIndex);
|
||||
ui->tokensTreeView->setCurrentIndex(newParameterExpressionIndex);
|
||||
ui->tokensTreeView->edit(newParameterExpressionIndex);
|
||||
});
|
||||
}
|
||||
|
||||
DlgThemeEditor::~DlgThemeEditor() = default;
|
||||
|
||||
void DlgThemeEditor::handleButtonClick(QAbstractButton* button)
|
||||
{
|
||||
auto role = ui->buttonBox->buttonRole(button);
|
||||
|
||||
switch (role) {
|
||||
case QDialogButtonBox::ApplyRole:
|
||||
case QDialogButtonBox::AcceptRole:
|
||||
model->flush();
|
||||
Application::Instance->reloadStyleSheet();
|
||||
break;
|
||||
case QDialogButtonBox::ResetRole:
|
||||
model->reset();
|
||||
break;
|
||||
default:
|
||||
// no-op
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Gui
|
||||
|
||||
#include "DlgThemeEditor.moc"
|
||||
@@ -0,0 +1,152 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <[email protected]> *
|
||||
* *
|
||||
* 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 DLGTHEMEEDITOR_H
|
||||
#define DLGTHEMEEDITOR_H
|
||||
|
||||
|
||||
#include "StyleParameters/ParameterManager.h"
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTreeView>
|
||||
#include <optional>
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QAbstractButton;
|
||||
QT_END_NAMESPACE
|
||||
|
||||
namespace Gui {
|
||||
|
||||
namespace StyleParameters
|
||||
{
|
||||
class ParameterManager;
|
||||
}
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
namespace Ui { class DlgThemeEditor; }
|
||||
QT_END_NAMESPACE
|
||||
|
||||
class GuiExport TokenTreeView : public QTreeView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
using QTreeView::QTreeView;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void requestRemove(const QModelIndex& index);
|
||||
};
|
||||
|
||||
class GuiExport StyleParametersModel: public QAbstractItemModel, public StyleParameters::ParameterSource
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
class Node;
|
||||
|
||||
public:
|
||||
struct Item;
|
||||
struct GroupItem;
|
||||
struct ParameterItem;
|
||||
|
||||
enum Column : std::uint8_t
|
||||
{
|
||||
ParameterName,
|
||||
ParameterExpression,
|
||||
ParameterType,
|
||||
ParameterPreview,
|
||||
ColumnCount
|
||||
};
|
||||
|
||||
FC_DISABLE_COPY_MOVE(StyleParametersModel);
|
||||
|
||||
explicit StyleParametersModel(const std::list<ParameterSource*>& sources,
|
||||
QObject* parent = nullptr);
|
||||
|
||||
~StyleParametersModel() override;
|
||||
|
||||
std::list<StyleParameters::Parameter> all() const override;
|
||||
std::optional<StyleParameters::Parameter> get(const std::string& name) const override;
|
||||
|
||||
void reset();
|
||||
void flush();
|
||||
|
||||
int rowCount(const QModelIndex& index) const override;
|
||||
int columnCount(const QModelIndex& index) const override;
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
|
||||
|
||||
Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
|
||||
QModelIndex index(int row, int col, const QModelIndex& parent) const override;
|
||||
QModelIndex parent(const QModelIndex& index) const override;
|
||||
|
||||
Node* node(const QModelIndex& index) const;
|
||||
Item* item(const QModelIndex& index) const;
|
||||
|
||||
template <typename T>
|
||||
T* item(const QModelIndex& index) const
|
||||
{
|
||||
return dynamic_cast<T*>(item(index));
|
||||
}
|
||||
|
||||
bool isAddPlaceholder(const QModelIndex& index) const;
|
||||
|
||||
public Q_SLOTS:
|
||||
void removeItem(const QModelIndex& index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void newParameterAdded(const QModelIndex& index);
|
||||
|
||||
private:
|
||||
std::list<ParameterSource*> sources;
|
||||
std::unique_ptr<StyleParameters::ParameterManager> manager;
|
||||
std::unique_ptr<Node> root;
|
||||
};
|
||||
|
||||
class GuiExport DlgThemeEditor : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
class Delegate;
|
||||
|
||||
public:
|
||||
FC_DISABLE_COPY_MOVE(DlgThemeEditor);
|
||||
|
||||
explicit DlgThemeEditor(QWidget *parent = nullptr);
|
||||
|
||||
~DlgThemeEditor() override;
|
||||
|
||||
public Q_SLOTS:
|
||||
void handleButtonClick(QAbstractButton* button);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::DlgThemeEditor> ui;
|
||||
std::unique_ptr<StyleParameters::ParameterManager> manager;
|
||||
std::unique_ptr<StyleParametersModel> model;
|
||||
};
|
||||
} // Gui
|
||||
|
||||
#endif //DLGTHEMEEDITOR_H
|
||||
@@ -0,0 +1,156 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Gui::DlgThemeEditor</class>
|
||||
<widget class="QDialog" name="Gui::DlgThemeEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1169</width>
|
||||
<height>562</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Theme Editor</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout" columnstretch="3,1">
|
||||
<item row="1" column="1">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Preview</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignVCenter</set>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="checkBox">
|
||||
<property name="text">
|
||||
<string>CheckBox</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QScrollBar" name="horizontalScrollBar">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QSpinBox" name="spinBox"/>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QRadioButton" name="radioButton">
|
||||
<property name="text">
|
||||
<string>RadioButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<widget class="QComboBox" name="comboBox">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Item 1</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Item 2</string>
|
||||
</property>
|
||||
</item>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QPushButton" name="pushButton">
|
||||
<property name="text">
|
||||
<string>PushButton</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<widget class="QWidget" name="tab">
|
||||
<attribute name="title">
|
||||
<string>Tab 1</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
<widget class="QWidget" name="tab_2">
|
||||
<attribute name="title">
|
||||
<string>Tab 2</string>
|
||||
</attribute>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::ColorButton" name="colorButton"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0" colspan="2">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::StandardButton::Apply|QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok|QDialogButtonBox::StandardButton::Reset</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="Gui::TokenTreeView" name="tokensTreeView">
|
||||
<property name="mouseTracking">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarPolicy::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::EditTrigger::DoubleClicked</set>
|
||||
</property>
|
||||
<property name="alternatingRowColors">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="selectionBehavior">
|
||||
<enum>QAbstractItemView::SelectionBehavior::SelectRows</enum>
|
||||
</property>
|
||||
<property name="rootIsDecorated">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="uniformRowHeights">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
<class>Gui::ColorButton</class>
|
||||
<extends>QPushButton</extends>
|
||||
<header>Gui/Widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::TokenTreeView</class>
|
||||
<extends>QTreeView</extends>
|
||||
<header location="global">Gui/Dialogs/DlgThemeEditor.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -133,6 +133,8 @@ public:
|
||||
if (activeStyleSheet.isEmpty()) {
|
||||
activeStyleSheet = _default;
|
||||
}
|
||||
|
||||
activeStyleSheet = Application::Instance->replaceVariablesInQss(activeStyleSheet);
|
||||
}
|
||||
|
||||
ParameterGrp::handle handle;
|
||||
@@ -157,7 +159,7 @@ private:
|
||||
}
|
||||
else if (!overlayStyleSheet.isEmpty() && !QFile::exists(overlayStyleSheet)) {
|
||||
// User did choose one of predefined stylesheets, we need to qualify it with namespace
|
||||
overlayStyleSheet = QStringLiteral("overlay:%1").arg(overlayStyleSheet);
|
||||
overlayStyleSheet = QStringLiteral("overlay:%1").arg(overlayStyleSheet);
|
||||
}
|
||||
|
||||
return overlayStyleSheet;
|
||||
|
||||
@@ -69,8 +69,10 @@
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <numbers>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <random>
|
||||
#include <ranges>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <stack>
|
||||
|
||||
@@ -41,9 +41,12 @@
|
||||
#include "DockWindowManager.h"
|
||||
#include "ToolBarManager.h"
|
||||
|
||||
#include <Application.h>
|
||||
#include <App/Application.h>
|
||||
|
||||
#include <ctime> // For generating a timestamped filename
|
||||
#include <ctime> // For generating a timestamped filename
|
||||
#include <Base/ServiceProvider.h>
|
||||
#include <Dialogs/DlgThemeEditor.h>
|
||||
|
||||
|
||||
using namespace Gui;
|
||||
@@ -359,6 +362,9 @@ bool PreferencePackManager::apply(const std::string& preferencePackName) const
|
||||
Gui::ToolBarManager* pToolbarMgr = Gui::ToolBarManager::getInstance();
|
||||
pToolbarMgr->restoreState();
|
||||
|
||||
// We need to reload stylesheet to apply any changed style parameters
|
||||
Gui::Application::Instance->reloadStyleSheet();
|
||||
|
||||
// TODO: Are there other things that have to be manually triggered?
|
||||
}
|
||||
return wasApplied;
|
||||
|
||||
@@ -627,6 +627,7 @@
|
||||
<FCText Name="Std_Delete">Del</FCText>
|
||||
<FCText Name="Std_DemoMode"></FCText>
|
||||
<FCText Name="Std_DependencyGraph"></FCText>
|
||||
<FCText Name="Std_DevHandbook"></FCText>
|
||||
<FCText Name="Std_DlgCustomize"></FCText>
|
||||
<FCText Name="Std_DlgMacroExecute"></FCText>
|
||||
<FCText Name="Std_DlgMacroExecuteDirect">Ctrl+F6</FCText>
|
||||
@@ -642,9 +643,7 @@
|
||||
<FCText Name="Std_Expressions"></FCText>
|
||||
<FCText Name="Std_FaceSelection"></FCText>
|
||||
<FCText Name="Std_FreeCADDonation"></FCText>
|
||||
<FCText Name="Std_FreeCADFAQ"></FCText>
|
||||
<FCText Name="Std_FreeCADForum"></FCText>
|
||||
<FCText Name="Std_FreeCADPowerUserHub"></FCText>
|
||||
<FCText Name="Std_FreeCADUserHub"></FCText>
|
||||
<FCText Name="Std_FreeCADWebsite"></FCText>
|
||||
<FCText Name="Std_FreezeViews"></FCText>
|
||||
@@ -690,7 +689,6 @@
|
||||
<FCText Name="Std_ProjectInfo"></FCText>
|
||||
<FCText Name="Std_ProjectUtil"></FCText>
|
||||
<FCText Name="Std_PythonHelp"></FCText>
|
||||
<FCText Name="Std_PythonWebsite"></FCText>
|
||||
<FCText Name="Std_Quit">Ctrl+Q</FCText>
|
||||
<FCText Name="Std_RandomColor"></FCText>
|
||||
<FCText Name="Std_RecallWorkingView">End</FCText>
|
||||
|
||||
@@ -31,6 +31,10 @@
|
||||
#include "DlgSettingsUI.h"
|
||||
#include "ui_DlgSettingsUI.h"
|
||||
|
||||
#include "Dialogs/DlgThemeEditor.h"
|
||||
|
||||
#include <Base/ServiceProvider.h>
|
||||
|
||||
|
||||
using namespace Gui::Dialog;
|
||||
|
||||
@@ -45,6 +49,10 @@ DlgSettingsUI::DlgSettingsUI(QWidget* parent)
|
||||
, ui(new Ui_DlgSettingsUI)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
connect(ui->themeEditorButton, &QPushButton::clicked, [this]() {
|
||||
openThemeEditor();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,13 +122,14 @@ void DlgSettingsUI::loadStyleSheet()
|
||||
populateStylesheets("OverlayActiveStyleSheet", "overlay", ui->OverlayStyleSheets, "Auto");
|
||||
}
|
||||
|
||||
void DlgSettingsUI::populateStylesheets(const char *key,
|
||||
const char *path,
|
||||
PrefComboBox *combo,
|
||||
const char *def,
|
||||
void DlgSettingsUI::populateStylesheets(const char* key,
|
||||
const char* path,
|
||||
PrefComboBox* combo,
|
||||
const char* def,
|
||||
QStringList filter)
|
||||
{
|
||||
auto hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/MainWindow");
|
||||
auto hGrp = App::GetApplication().GetParameterGroupByPath(
|
||||
"User parameter:BaseApp/Preferences/MainWindow");
|
||||
// List all .qss/.css files
|
||||
QMap<QString, QString> cssFiles;
|
||||
QDir dir;
|
||||
@@ -172,6 +181,12 @@ void DlgSettingsUI::populateStylesheets(const char *key,
|
||||
combo->onRestore();
|
||||
}
|
||||
|
||||
void DlgSettingsUI::openThemeEditor()
|
||||
{
|
||||
Gui::DlgThemeEditor editor;
|
||||
editor.exec();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the strings of the subwidgets using the current language.
|
||||
*/
|
||||
@@ -190,6 +205,10 @@ namespace {
|
||||
|
||||
void applyStyleSheet(ParameterGrp *hGrp)
|
||||
{
|
||||
if (auto parameterManager = Base::provideService<Gui::StyleParameters::ParameterManager>()) {
|
||||
parameterManager->reload();
|
||||
}
|
||||
|
||||
auto sheet = hGrp->GetASCII("StyleSheet");
|
||||
bool tiledBG = hGrp->GetBool("TiledBackground", false);
|
||||
Gui::Application::Instance->setStyleSheet(QString::fromUtf8(sheet.c_str()), tiledBG);
|
||||
|
||||
@@ -62,6 +62,7 @@ protected:
|
||||
const char *def,
|
||||
QStringList filter = QStringList());
|
||||
|
||||
void openThemeEditor();
|
||||
private:
|
||||
std::unique_ptr<Ui_DlgSettingsUI> ui;
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" columnstretch="2,1,0">
|
||||
<layout class="QGridLayout" columnstretch="2,1">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label1">
|
||||
<property name="text">
|
||||
@@ -53,7 +53,7 @@
|
||||
<property name="toolTip">
|
||||
<string>This color might be used by your theme to let you customize it.</string>
|
||||
</property>
|
||||
<property name="color" stdset="0">
|
||||
<property name="color">
|
||||
<color>
|
||||
<red>85</red>
|
||||
<green>123</green>
|
||||
@@ -119,7 +119,7 @@
|
||||
<property name="toolTip">
|
||||
<string>This color might be used by your theme to let you customize it.</string>
|
||||
</property>
|
||||
<property name="color" stdset="0">
|
||||
<property name="color">
|
||||
<color>
|
||||
<red>85</red>
|
||||
<green>123</green>
|
||||
@@ -145,7 +145,7 @@
|
||||
<property name="toolTip">
|
||||
<string>This color might be used by your theme to let you customize it.</string>
|
||||
</property>
|
||||
<property name="color" stdset="0">
|
||||
<property name="color">
|
||||
<color>
|
||||
<red>85</red>
|
||||
<green>123</green>
|
||||
@@ -165,12 +165,12 @@
|
||||
<property name="toolTip">
|
||||
<string>Style sheet how user interface will look like</string>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>MainWindow</cstring>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>StyleSheet</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>MainWindow</cstring>
|
||||
</property>
|
||||
<property name="prefType" stdset="0">
|
||||
<cstring></cstring>
|
||||
</property>
|
||||
@@ -178,12 +178,12 @@
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="Gui::PrefComboBox" name="OverlayStyleSheets">
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>MainWindow</cstring>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>OverlayActiveStyleSheet</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>MainWindow</cstring>
|
||||
</property>
|
||||
<property name="prefType" stdset="0">
|
||||
<cstring></cstring>
|
||||
</property>
|
||||
@@ -191,6 +191,13 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="themeEditorButton">
|
||||
<property name="text">
|
||||
<string>Open Theme Editor</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
@@ -230,12 +237,12 @@
|
||||
<property name="value">
|
||||
<number>16</number>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>TreeView</cstring>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>IconSize</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>TreeView</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
@@ -285,12 +292,12 @@
|
||||
<property name="value">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>TreeView</cstring>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>ItemSpacing</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>TreeView</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0" colspan="2">
|
||||
@@ -492,7 +499,7 @@
|
||||
<item>
|
||||
<spacer name="spacer_3">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
<enum>Qt::Orientation::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
@@ -510,26 +517,26 @@
|
||||
<extends>QPushButton</extends>
|
||||
<header>Gui/Widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefColorButton</class>
|
||||
<extends>Gui::ColorButton</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefSpinBox</class>
|
||||
<extends>QSpinBox</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefColorButton</class>
|
||||
<extends>Gui::ColorButton</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefCheckBox</class>
|
||||
<extends>QCheckBox</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>Gui::PrefComboBox</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>Gui/PrefWidgets.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<tabstops>
|
||||
<tabstop>ThemeAccentColor1</tabstop>
|
||||
|
||||
@@ -0,0 +1,353 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <[email protected]> *
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include "ParameterManager.h"
|
||||
#include "Parser.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QColor>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <ranges>
|
||||
#include <variant>
|
||||
#endif
|
||||
|
||||
namespace Gui::StyleParameters
|
||||
{
|
||||
|
||||
Length Length::operator+(const Length& rhs) const
|
||||
{
|
||||
ensureEqualUnits(rhs);
|
||||
return {value + rhs.value, unit};
|
||||
}
|
||||
|
||||
Length Length::operator-(const Length& rhs) const
|
||||
{
|
||||
ensureEqualUnits(rhs);
|
||||
return {value - rhs.value, unit};
|
||||
}
|
||||
|
||||
Length Length::operator-() const
|
||||
{
|
||||
return {-value, unit};
|
||||
}
|
||||
|
||||
Length Length::operator/(const Length& rhs) const
|
||||
{
|
||||
if (rhs.value == 0) {
|
||||
THROWM(Base::RuntimeError, "Division by zero");
|
||||
}
|
||||
|
||||
if (rhs.unit.empty() || unit.empty()) {
|
||||
return {value / rhs.value, unit};
|
||||
}
|
||||
|
||||
ensureEqualUnits(rhs);
|
||||
return {value / rhs.value, unit};
|
||||
}
|
||||
|
||||
Length Length::operator*(const Length& rhs) const
|
||||
{
|
||||
if (rhs.unit.empty() || unit.empty()) {
|
||||
return {value * rhs.value, unit};
|
||||
}
|
||||
|
||||
ensureEqualUnits(rhs);
|
||||
return {value * rhs.value, unit};
|
||||
}
|
||||
|
||||
void Length::ensureEqualUnits(const Length& rhs) const
|
||||
{
|
||||
if (unit != rhs.unit) {
|
||||
THROWM(Base::RuntimeError,
|
||||
fmt::format("Units mismatch left expression is '{}', right expression is '{}'",
|
||||
unit,
|
||||
rhs.unit));
|
||||
}
|
||||
}
|
||||
|
||||
std::string Value::toString() const
|
||||
{
|
||||
if (std::holds_alternative<Length>(*this)) {
|
||||
auto [value, unit] = std::get<Length>(*this);
|
||||
return fmt::format("{}{}", value, unit);
|
||||
}
|
||||
|
||||
if (std::holds_alternative<QColor>(*this)) {
|
||||
auto color = std::get<QColor>(*this);
|
||||
return fmt::format("#{:0>6x}", 0xFFFFFF & color.rgb()); // NOLINT(*-magic-numbers)
|
||||
}
|
||||
|
||||
return std::get<std::string>(*this);
|
||||
}
|
||||
|
||||
ParameterSource::ParameterSource(const Metadata& metadata)
|
||||
: metadata(metadata)
|
||||
{}
|
||||
|
||||
InMemoryParameterSource::InMemoryParameterSource(const std::list<Parameter>& parameters,
|
||||
const Metadata& metadata)
|
||||
: ParameterSource(metadata)
|
||||
{
|
||||
for (const auto& parameter : parameters) {
|
||||
InMemoryParameterSource::define(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
std::list<Parameter> InMemoryParameterSource::all() const
|
||||
{
|
||||
auto values = parameters | std::ranges::views::values;
|
||||
|
||||
return std::list<Parameter>(values.begin(), values.end());
|
||||
}
|
||||
|
||||
std::optional<Parameter> InMemoryParameterSource::get(const std::string& name) const
|
||||
{
|
||||
if (parameters.contains(name)) {
|
||||
return parameters.at(name);
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void InMemoryParameterSource::define(const Parameter& parameter)
|
||||
{
|
||||
parameters[parameter.name] = parameter;
|
||||
}
|
||||
|
||||
void InMemoryParameterSource::remove(const std::string& name)
|
||||
{
|
||||
parameters.erase(name);
|
||||
}
|
||||
|
||||
BuiltInParameterSource::BuiltInParameterSource(const Metadata& metadata)
|
||||
: ParameterSource(metadata)
|
||||
{
|
||||
this->metadata.options |= ReadOnly;
|
||||
}
|
||||
|
||||
std::list<Parameter> BuiltInParameterSource::all() const
|
||||
{
|
||||
std::list<Parameter> result;
|
||||
|
||||
for (const auto& name : params | std::views::keys) {
|
||||
result.push_back(*get(name));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Parameter> BuiltInParameterSource::get(const std::string& name) const
|
||||
{
|
||||
if (params.contains(name)) {
|
||||
unsigned long color = params.at(name)->GetUnsigned(name.c_str(), 0);
|
||||
|
||||
return Parameter {
|
||||
.name = name,
|
||||
.value = fmt::format("#{:0>6x}", 0x00FFFFFF & (color >> 8)), // NOLINT(*-magic-numbers)
|
||||
};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
UserParameterSource::UserParameterSource(ParameterGrp::handle hGrp, const Metadata& metadata)
|
||||
: ParameterSource(metadata)
|
||||
, hGrp(hGrp)
|
||||
{}
|
||||
|
||||
std::list<Parameter> UserParameterSource::all() const
|
||||
{
|
||||
std::list<Parameter> result;
|
||||
|
||||
for (const auto& [token, value] : hGrp->GetASCIIMap()) {
|
||||
result.push_back({
|
||||
.name = token,
|
||||
.value = value,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
std::optional<Parameter> UserParameterSource::get(const std::string& name) const
|
||||
{
|
||||
if (auto value = hGrp->GetASCII(name.c_str(), ""); !value.empty()) {
|
||||
return Parameter {
|
||||
.name = name,
|
||||
.value = value,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void UserParameterSource::define(const Parameter& parameter)
|
||||
{
|
||||
hGrp->SetASCII(parameter.name.c_str(), parameter.value);
|
||||
}
|
||||
|
||||
void UserParameterSource::remove(const std::string& name)
|
||||
{
|
||||
hGrp->RemoveASCII(name.c_str());
|
||||
}
|
||||
|
||||
ParameterManager::ParameterManager() = default;
|
||||
|
||||
void ParameterManager::reload()
|
||||
{
|
||||
_resolved.clear();
|
||||
}
|
||||
|
||||
std::string ParameterManager::replacePlaceholders(const std::string& expression,
|
||||
ResolveContext context) const
|
||||
{
|
||||
static const QRegularExpression regex(QStringLiteral("@(\\w+)"));
|
||||
|
||||
auto substituteWithCallback =
|
||||
[](const QRegularExpression& regex,
|
||||
const QString& input,
|
||||
const std::function<QString(const QRegularExpressionMatch&)>& callback) {
|
||||
QRegularExpressionMatchIterator it = regex.globalMatch(input);
|
||||
|
||||
QString result;
|
||||
qsizetype lastIndex = 0;
|
||||
|
||||
while (it.hasNext()) {
|
||||
QRegularExpressionMatch match = it.next();
|
||||
|
||||
qsizetype start = match.capturedStart();
|
||||
qsizetype end = match.capturedEnd();
|
||||
|
||||
result += input.mid(lastIndex, start - lastIndex);
|
||||
result += callback(match);
|
||||
|
||||
lastIndex = end;
|
||||
}
|
||||
|
||||
// Append any remaining text after the last match
|
||||
result += input.mid(lastIndex);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
return substituteWithCallback(
|
||||
regex,
|
||||
QString::fromStdString(expression),
|
||||
[&](const QRegularExpressionMatch& match) {
|
||||
auto tokenName = match.captured(1).toStdString();
|
||||
|
||||
auto tokenValue = resolve(tokenName, context);
|
||||
context.visited.erase(tokenName);
|
||||
|
||||
return QString::fromStdString(tokenValue.toString());
|
||||
}
|
||||
).toStdString();
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
std::list<Parameter> ParameterManager::parameters() const
|
||||
{
|
||||
std::set<Parameter, Parameter::NameComparator> result;
|
||||
|
||||
// we need to traverse it in reverse order so more important tokens will take precedence
|
||||
for (const ParameterSource* source : _sources | std::views::reverse) {
|
||||
for (const Parameter& parameter : source->all()) {
|
||||
result.insert(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return std::list(result.begin(), result.end());
|
||||
}
|
||||
|
||||
std::optional<std::string> ParameterManager::expression(const std::string& name) const
|
||||
{
|
||||
if (auto param = parameter(name)) {
|
||||
return param->value;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
Value ParameterManager::resolve(const std::string& name, ResolveContext context) const
|
||||
{
|
||||
std::optional<Parameter> maybeParameter = this->parameter(name);
|
||||
|
||||
if (!maybeParameter) {
|
||||
Base::Console().warning("Requested non-existent design token '%s'.", name);
|
||||
return std::string {};
|
||||
}
|
||||
|
||||
if (context.visited.contains(name)) {
|
||||
Base::Console().warning("The design token '%s' contains circular-reference.", name);
|
||||
return expression(name).value_or(std::string {});
|
||||
}
|
||||
|
||||
const Parameter& token = *maybeParameter;
|
||||
|
||||
if (!_resolved.contains(token.name)) {
|
||||
context.visited.insert(token.name);
|
||||
try {
|
||||
_resolved[token.name] = evaluate(token.value, context);
|
||||
}
|
||||
catch (Base::Exception&) {
|
||||
// in case of being unable to parse it, we need to treat it as a generic value
|
||||
_resolved[token.name] = replacePlaceholders(token.value, context);
|
||||
}
|
||||
context.visited.erase(token.name);
|
||||
}
|
||||
|
||||
return _resolved[token.name];
|
||||
}
|
||||
|
||||
Value ParameterManager::evaluate(const std::string& expression, ResolveContext context) const
|
||||
{
|
||||
Parser parser(expression);
|
||||
return parser.parse()->evaluate({.manager = this, .context = std::move(context)});
|
||||
}
|
||||
|
||||
std::optional<Parameter> ParameterManager::parameter(const std::string& name) const
|
||||
{
|
||||
for (const ParameterSource* source : _sources) {
|
||||
if (const auto& parameter = source->get(name)) {
|
||||
return parameter;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void ParameterManager::addSource(ParameterSource* source)
|
||||
{
|
||||
_sources.push_front(source);
|
||||
}
|
||||
|
||||
std::list<ParameterSource*> ParameterManager::sources() const
|
||||
{
|
||||
return _sources;
|
||||
}
|
||||
|
||||
} // namespace Gui::StyleParameters
|
||||
@@ -0,0 +1,457 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <[email protected]> *
|
||||
* *
|
||||
* 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 STYLEPARAMETERS_PARAMETERMANAGER_H
|
||||
#define STYLEPARAMETERS_PARAMETERMANAGER_H
|
||||
|
||||
#include <list>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include <App/Application.h>
|
||||
#include <Base/Bitmask.h>
|
||||
#include <Base/Parameter.h>
|
||||
|
||||
namespace Gui::StyleParameters
|
||||
{
|
||||
|
||||
// Forward declaration for Parser
|
||||
class Parser;
|
||||
|
||||
/**
|
||||
* @brief Represents a length in a specified unit.
|
||||
*
|
||||
* This struct is a very simplified representation of lengths that can be used as parameters for
|
||||
* styling purposes. The length basically consists of value and unit. Unit is optional, empty unit
|
||||
* represents a dimensionless length that can be used as a scalar. This struct does not care about
|
||||
* unit conversions as its uses do not require it.
|
||||
*/
|
||||
struct Length
|
||||
{
|
||||
/// Numeric value of the length.
|
||||
double value;
|
||||
/// Unit of the length, empty if the value is dimensionless.
|
||||
std::string unit;
|
||||
|
||||
/**
|
||||
* @name Operators
|
||||
*
|
||||
* This struct supports basic operations on Length. Each operation requires for operands to be
|
||||
* the same unit. Multiplication and division additionally allow one operand to be dimensionless
|
||||
* and hence act as a scalar.
|
||||
*
|
||||
* @code{c++}
|
||||
* Length a { 10, "px" };
|
||||
* Length b { 5, "px" };
|
||||
*
|
||||
* Length differentUnit { 3, "rem" }
|
||||
* Length scalar { 2, "" };
|
||||
*
|
||||
* // basic operations with the same unit are allowed
|
||||
* auto sum = a + b; // 15 px
|
||||
* auto difference = a - 5; // 10 px
|
||||
*
|
||||
* // basic operations with mixed units are NOT allowed
|
||||
* auto sumOfIncompatibleUnits = a + differentUnit; // will throw
|
||||
* auto productOfIncompatibleUnits = a * differentUnit; // will throw
|
||||
*
|
||||
* // exception is that for multiplication and division dimensionless units are allowed
|
||||
* auto productWithScalar = a * scalar; // 20 px
|
||||
* @endcode
|
||||
* @{
|
||||
*/
|
||||
Length operator+(const Length& rhs) const;
|
||||
Length operator-(const Length& rhs) const;
|
||||
Length operator-() const;
|
||||
|
||||
Length operator/(const Length& rhs) const;
|
||||
Length operator*(const Length& rhs) const;
|
||||
/// @}
|
||||
|
||||
private:
|
||||
void ensureEqualUnits(const Length& rhs) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief This struct represents any valid value that can be used as the parameter value.
|
||||
*
|
||||
* The value can be one of three basic types:
|
||||
* - Numbers / Lengths (so any length with optional unit) (Length)
|
||||
* - Colors (QColor)
|
||||
* - Any other generic expression. (std::string)
|
||||
*
|
||||
* As a rule, operations can be only performed over values of the same type.
|
||||
*/
|
||||
struct Value : std::variant<Length, QColor, std::string>
|
||||
{
|
||||
using std::variant<Length, QColor, std::string>::variant;
|
||||
|
||||
/**
|
||||
* Converts the object into its string representation.
|
||||
*
|
||||
* @return A string representation of the object that can later be used in QSS.
|
||||
*/
|
||||
std::string toString() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @struct Parameter
|
||||
*
|
||||
* @brief Represents a named, dynamic expression-based parameter.
|
||||
*
|
||||
* The Parameter structure is used to define reusable named variables in styling or layout systems.
|
||||
* Each parameter consists of a `name` and a `value` string, where the value is a CSS-like expression
|
||||
* that supports numbers, units, arithmetic, colors, functions, and parameter references.
|
||||
*
|
||||
* ### Naming Convention
|
||||
* Parameter names must be unique and follow **CamelCase**.
|
||||
*/
|
||||
struct Parameter
|
||||
{
|
||||
/// Comparator that assumes that parameters are equal as long as name is the same
|
||||
struct NameComparator
|
||||
{
|
||||
bool operator()(const Parameter& lhs, const Parameter& rhs) const
|
||||
{
|
||||
return lhs.name < rhs.name;
|
||||
}
|
||||
};
|
||||
|
||||
/// Name of the parameter, name should follow CamelCase
|
||||
std::string name;
|
||||
/// Expression associated with the parameter
|
||||
std::string value;
|
||||
};
|
||||
|
||||
enum class ParameterSourceOption
|
||||
{
|
||||
// clang-format off
|
||||
/// Parameters are read-only and the source does not allow editing
|
||||
ReadOnly = 1 << 0,
|
||||
/// Parameters are expected to be edited by the user, not only theme developers
|
||||
UserEditable = 1 << 1,
|
||||
// clang-format on
|
||||
};
|
||||
|
||||
using ParameterSourceOptions = Base::Flags<ParameterSourceOption>;
|
||||
|
||||
/**
|
||||
* @brief Abstract base class representing a source of style parameters.
|
||||
*
|
||||
* A `ParameterSource` is responsible for managing a collection of named parameters. Each source
|
||||
* has metadata describing its type, characteristics, and behavior.
|
||||
*
|
||||
* ### Key Responsibilities
|
||||
* - Define, update, and remove parameters within the source.
|
||||
* - Provide access to all parameters or specific ones by name.
|
||||
* - Act as a backend for parameter management, feeding the `ParameterManager` with available
|
||||
* parameter data.
|
||||
*
|
||||
* ### Metadata
|
||||
* Each parameter source includes metadata consisting of:
|
||||
* - `name`: Name of the source, for identification purposes.
|
||||
* - `options`: Flags specifying optional behavior (e.g., `ReadOnly`, `UserEditable`).
|
||||
*
|
||||
* ### Notes on Usage
|
||||
* - Subclasses of `ParameterSource` (e.g., `BuiltInParameterSource`, `UserParameterSource`)
|
||||
* implement different storage mechanisms and behaviors based on whether parameters are
|
||||
* pre-defined, user-defined, or loaded in memory.
|
||||
* - Parameters can be retrieved and manipulated globally through the `ParameterManager`, which
|
||||
* aggregates multiple `ParameterSource` instances.
|
||||
*
|
||||
* #### Example
|
||||
* @code{.cpp}
|
||||
* // Create an in-memory parameter source
|
||||
* InMemoryParameterSource source({
|
||||
* Parameter{ "BasePadding", "16px" },
|
||||
* Parameter{ "DefaultColor", "#ff00ff" },
|
||||
* });
|
||||
*
|
||||
* source.define(Parameter{ "Margin", "4px" }); // Adds a new parameter
|
||||
*
|
||||
* auto padding = source.get("BasePadding"); // Retrieves parameter named "BasePadding"
|
||||
* auto parametersList = source.all(); // Retrieve all parameters
|
||||
* @endcode
|
||||
*
|
||||
* ### Subclass Requirements
|
||||
* Derived classes must implement:
|
||||
* - `all()` - Retrieve all parameters in the source.
|
||||
* - `get()` - Retrieve a specific parameter.
|
||||
* - `define()` - Add or update a parameter, can be left empty for readonly sources.
|
||||
* - `remove()` - Remove a parameter by name, can be left empty for readonly sources.
|
||||
*/
|
||||
class GuiExport ParameterSource
|
||||
{
|
||||
public:
|
||||
using enum ParameterSourceOption;
|
||||
|
||||
/**
|
||||
* @brief Contains metadata information about a `ParameterSource`.
|
||||
*
|
||||
* The `Metadata` struct provides a way to describe the characteristics and identity
|
||||
* of a `ParameterSource`. It includes a name for identification and a set of options
|
||||
* that define the source's behavior and restrictions.
|
||||
*/
|
||||
struct Metadata
|
||||
{
|
||||
/// The name of the parameter source. Should be marked for translation using QT_TR_NOOP
|
||||
std::string name;
|
||||
/// Flags defining the behavior and properties of the parameter source.
|
||||
ParameterSourceOptions options {};
|
||||
};
|
||||
|
||||
/// Metadata of the parameter source
|
||||
Metadata metadata;
|
||||
|
||||
FC_DEFAULT_MOVE(ParameterSource);
|
||||
FC_DISABLE_COPY(ParameterSource);
|
||||
|
||||
explicit ParameterSource(const Metadata& metadata);
|
||||
virtual ~ParameterSource() = default;
|
||||
|
||||
/**
|
||||
* @brief Retrieves a list of all parameters available in the source.
|
||||
*
|
||||
* This method returns every parameter defined within this source, enabling iteration and bulk
|
||||
* access to all stored values.
|
||||
*
|
||||
* @return A list containing all `Parameter` objects stored in the source.
|
||||
*/
|
||||
virtual std::list<Parameter> all() const = 0;
|
||||
|
||||
/**
|
||||
* @brief Retrieves a specific parameter by its name.
|
||||
*
|
||||
* @param[in] name The name of the parameter to retrieve.
|
||||
* @return An optional containing the requested parameter if it exists, or empty if not.
|
||||
*/
|
||||
virtual std::optional<Parameter> get(const std::string& name) const = 0;
|
||||
|
||||
/**
|
||||
* @brief Defines or updates a parameter in the source.
|
||||
*
|
||||
* Adds a new parameter to the source if it doesn't already exist or updates the value of an
|
||||
* existing parameter with the same name.
|
||||
*
|
||||
* @param[in] parameter The `Parameter` object to define or update in the source.
|
||||
*/
|
||||
virtual void define([[maybe_unused]] const Parameter& parameter) {}
|
||||
|
||||
/**
|
||||
* @brief Removes a parameter from the source by its name.
|
||||
*
|
||||
* Deletes the specific parameter from the source if it exists. If no parameter with the given
|
||||
* name is found, the method does nothing.
|
||||
*
|
||||
* @param[in] name The name of the parameter to remove.
|
||||
*/
|
||||
virtual void remove([[maybe_unused]] const std::string& name) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief In-memory parameter source that stores parameters in a map.
|
||||
*
|
||||
* This source is useful for temporary parameter storage or when you need to
|
||||
* define parameters programmatically without persisting them to disk.
|
||||
*/
|
||||
class GuiExport InMemoryParameterSource : public ParameterSource
|
||||
{
|
||||
std::map<std::string, Parameter> parameters;
|
||||
|
||||
public:
|
||||
InMemoryParameterSource(const std::list<Parameter>& parameters, const Metadata& metadata);
|
||||
|
||||
std::list<Parameter> all() const override;
|
||||
std::optional<Parameter> get(const std::string& name) const override;
|
||||
void define(const Parameter& parameter) override;
|
||||
void remove(const std::string& name) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Built-in parameter source that reads from FreeCAD's parameter system.
|
||||
*
|
||||
* This source provides access to predefined parameters that are stored in
|
||||
* FreeCAD's global parameter system. These parameters are typically defined
|
||||
* by the application and are read-only.
|
||||
*/
|
||||
class GuiExport BuiltInParameterSource : public ParameterSource
|
||||
{
|
||||
public:
|
||||
explicit BuiltInParameterSource(const Metadata& metadata = {});
|
||||
|
||||
std::list<Parameter> all() const override;
|
||||
std::optional<Parameter> get(const std::string& name) const override;
|
||||
|
||||
private:
|
||||
ParameterGrp::handle hGrpThemes =
|
||||
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Themes");
|
||||
ParameterGrp::handle hGrpView =
|
||||
App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View");
|
||||
|
||||
std::map<std::string, ParameterGrp::handle> params = {
|
||||
{"ThemeAccentColor1", hGrpThemes},
|
||||
{"ThemeAccentColor2", hGrpThemes},
|
||||
{"ThemeAccentColor3", hGrpThemes},
|
||||
{"BackgroundColor", hGrpView},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief User-defined parameter source that reads from user preferences.
|
||||
*
|
||||
* This source provides access to user-defined parameters that are stored
|
||||
* in the user's preference file. These parameters can be modified by the
|
||||
* user and persist across application sessions.
|
||||
*/
|
||||
class GuiExport UserParameterSource : public ParameterSource
|
||||
{
|
||||
ParameterGrp::handle hGrp;
|
||||
|
||||
public:
|
||||
UserParameterSource(ParameterGrp::handle hGrp, const Metadata& metadata);
|
||||
|
||||
std::list<Parameter> all() const override;
|
||||
std::optional<Parameter> get(const std::string& name) const override;
|
||||
void define(const Parameter& parameter) override;
|
||||
void remove(const std::string& name) override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Central manager for style parameters that aggregates multiple sources.
|
||||
*
|
||||
* The ParameterManager is responsible for:
|
||||
* - Managing multiple parameter sources
|
||||
* - Resolving parameter references and expressions
|
||||
* - Caching resolved values for performance
|
||||
* - Handling circular references
|
||||
*/
|
||||
class GuiExport ParameterManager
|
||||
{
|
||||
std::list<ParameterSource*> _sources;
|
||||
mutable std::map<std::string, Value> _resolved;
|
||||
|
||||
public:
|
||||
struct ResolveContext
|
||||
{
|
||||
/// Names of parameters currently being resolved.
|
||||
std::set<std::string> visited;
|
||||
};
|
||||
|
||||
ParameterManager();
|
||||
|
||||
/**
|
||||
* @brief Clears the internal cache of resolved values.
|
||||
*
|
||||
* Call this method when parameters have been modified to ensure
|
||||
* that the changes are reflected in subsequent resolutions.
|
||||
*/
|
||||
void reload();
|
||||
|
||||
/**
|
||||
* @brief Replaces parameter placeholders in a string with their resolved values.
|
||||
*
|
||||
* This method performs simple string substitution of @parameter references
|
||||
* with their actual values. It does not evaluate expressions, only performs
|
||||
* direct substitution.
|
||||
*
|
||||
* @param expression The string containing parameter placeholders
|
||||
* @param context Resolution context for handling circular references
|
||||
* @return The string with all placeholders replaced
|
||||
*/
|
||||
std::string replacePlaceholders(const std::string& expression, ResolveContext context = {}) const;
|
||||
|
||||
/**
|
||||
* @brief Returns all available parameters from all sources.
|
||||
*
|
||||
* Parameters are returned in order of source priority, with later sources
|
||||
* taking precedence over earlier ones.
|
||||
*
|
||||
* @return List of all available parameters
|
||||
*/
|
||||
std::list<Parameter> parameters() const;
|
||||
|
||||
/**
|
||||
* @brief Gets the raw expression string for a parameter.
|
||||
*
|
||||
* @param name The name of the parameter
|
||||
* @return The expression string if the parameter exists, empty otherwise
|
||||
*/
|
||||
std::optional<std::string> expression(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Resolves a parameter to its final value.
|
||||
*
|
||||
* This method evaluates the parameter's expression and returns the computed
|
||||
* value. The result is cached for subsequent calls.
|
||||
*
|
||||
* @param name The name of the parameter to resolve
|
||||
* @param context Resolution context for handling circular references
|
||||
* @return The resolved value
|
||||
*/
|
||||
Value resolve(const std::string& name, ResolveContext context = {}) const;
|
||||
|
||||
/**
|
||||
* @brief Evaluates an expression string and returns the result.
|
||||
*
|
||||
* @param expression The expression to evaluate
|
||||
* @param context Resolution context for handling circular references
|
||||
* @return The evaluated value
|
||||
*/
|
||||
Value evaluate(const std::string& expression, ResolveContext context = {}) const;
|
||||
|
||||
/**
|
||||
* @brief Gets a parameter by name from any source.
|
||||
*
|
||||
* @param name The name of the parameter
|
||||
* @return The parameter if found, empty otherwise
|
||||
*/
|
||||
std::optional<Parameter> parameter(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief Adds a parameter source to the manager.
|
||||
*
|
||||
* Sources are evaluated in the order they are added, with later sources
|
||||
* taking precedence over earlier ones.
|
||||
*
|
||||
* @param source The parameter source to add
|
||||
*/
|
||||
void addSource(ParameterSource* source);
|
||||
|
||||
/**
|
||||
* @brief Returns all registered parameter sources.
|
||||
*
|
||||
* @return List of parameter sources in order of registration
|
||||
*/
|
||||
std::list<ParameterSource*> sources() const;
|
||||
};
|
||||
|
||||
} // namespace Gui::StyleParameters
|
||||
|
||||
ENABLE_BITMASK_OPERATORS(Gui::StyleParameters::ParameterSourceOption);
|
||||
|
||||
#endif // STYLEPARAMETERS_PARAMETERMANAGER_H
|
||||
@@ -0,0 +1,403 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <[email protected]> *
|
||||
* *
|
||||
* 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/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#include "Parser.h"
|
||||
#include "ParameterManager.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <QColor>
|
||||
#include <QRegularExpression>
|
||||
#include <QString>
|
||||
#include <ranges>
|
||||
#include <variant>
|
||||
#endif
|
||||
|
||||
namespace Gui::StyleParameters
|
||||
{
|
||||
|
||||
Value ParameterReference::evaluate(const EvaluationContext& context) const
|
||||
{
|
||||
return context.manager->resolve(name, context.context);
|
||||
}
|
||||
|
||||
Value Number::evaluate([[maybe_unused]] const EvaluationContext& context) const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
Value Color::evaluate([[maybe_unused]] const EvaluationContext& context) const
|
||||
{
|
||||
return color;
|
||||
}
|
||||
|
||||
Value FunctionCall::evaluate(const EvaluationContext& context) const
|
||||
{
|
||||
if (arguments.size() != 2) {
|
||||
THROWM(Base::ExpressionError,
|
||||
fmt::format("Function '{}' expects 2 arguments, got {}",
|
||||
functionName,
|
||||
arguments.size()));
|
||||
}
|
||||
|
||||
auto colorArg = arguments[0]->evaluate(context);
|
||||
auto amountArg = arguments[1]->evaluate(context);
|
||||
|
||||
if (!std::holds_alternative<QColor>(colorArg)) {
|
||||
THROWM(Base::ExpressionError,
|
||||
fmt::format("'{}' is not supported for colors", functionName));
|
||||
}
|
||||
|
||||
auto color = std::get<QColor>(colorArg);
|
||||
|
||||
// In Qt if you want to make color 20% darker or lighter, you need to pass 120 as the value
|
||||
// we, however, want users to pass only the relative difference, hence we need to add the
|
||||
// 100 required by Qt.
|
||||
//
|
||||
// NOLINTNEXTLINE(*-magic-numbers)
|
||||
auto amount = 100 + static_cast<int>(std::get<Length>(amountArg).value);
|
||||
|
||||
if (functionName == "lighten") {
|
||||
return color.lighter(amount);
|
||||
}
|
||||
|
||||
if (functionName == "darken") {
|
||||
return color.darker(amount);
|
||||
}
|
||||
|
||||
THROWM(Base::ExpressionError, fmt::format("Unknown function '{}'", functionName));
|
||||
}
|
||||
|
||||
Value BinaryOp::evaluate(const EvaluationContext& context) const
|
||||
{
|
||||
Value lval = left->evaluate(context);
|
||||
Value rval = right->evaluate(context);
|
||||
|
||||
if (!std::holds_alternative<Length>(lval) || !std::holds_alternative<Length>(rval)) {
|
||||
THROWM(Base::ExpressionError, "Math operations are supported only on lengths");
|
||||
}
|
||||
|
||||
auto lhs = std::get<Length>(lval);
|
||||
auto rhs = std::get<Length>(rval);
|
||||
|
||||
switch (op) {
|
||||
case Operator::Add:
|
||||
return lhs + rhs;
|
||||
case Operator::Subtract:
|
||||
return lhs - rhs;
|
||||
case Operator::Multiply:
|
||||
return lhs * rhs;
|
||||
case Operator::Divide:
|
||||
return lhs / rhs;
|
||||
default:
|
||||
THROWM(Base::ExpressionError, "Unknown operator");
|
||||
}
|
||||
}
|
||||
|
||||
Value UnaryOp::evaluate(const EvaluationContext& context) const
|
||||
{
|
||||
Value val = operand->evaluate(context);
|
||||
if (std::holds_alternative<QColor>(val)) {
|
||||
THROWM(Base::ExpressionError, "Unary operations on colors are not supported");
|
||||
}
|
||||
|
||||
auto v = std::get<Length>(val);
|
||||
switch (op) {
|
||||
case Operator::Add:
|
||||
return v;
|
||||
case Operator::Subtract:
|
||||
return -v;
|
||||
default:
|
||||
THROWM(Base::ExpressionError, "Unknown unary operator");
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parse()
|
||||
{
|
||||
auto expr = parseExpression();
|
||||
skipWhitespace();
|
||||
if (pos != input.size()) {
|
||||
THROWM(Base::ParserError,
|
||||
fmt::format("Unexpected characters at end of input: {}", input.substr(pos)));
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
bool Parser::peekString(const char* function) const
|
||||
{
|
||||
return input.compare(pos, strlen(function), function) == 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parseExpression()
|
||||
{
|
||||
auto expr = parseTerm();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (match('+')) {
|
||||
expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Add, parseTerm());
|
||||
}
|
||||
else if (match('-')) {
|
||||
expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Subtract, parseTerm());
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parseTerm()
|
||||
{
|
||||
auto expr = parseFactor();
|
||||
while (true) {
|
||||
skipWhitespace();
|
||||
if (match('*')) {
|
||||
expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Multiply, parseFactor());
|
||||
}
|
||||
else if (match('/')) {
|
||||
expr = std::make_unique<BinaryOp>(std::move(expr), Operator::Divide, parseFactor());
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parseFactor()
|
||||
{
|
||||
skipWhitespace();
|
||||
if (match('+') || match('-')) {
|
||||
Operator op = (input[pos - 1] == '+') ? Operator::Add : Operator::Subtract;
|
||||
return std::make_unique<UnaryOp>(op, parseFactor());
|
||||
}
|
||||
if (match('(')) {
|
||||
auto expr = parseExpression();
|
||||
if (!match(')')) {
|
||||
THROWM(Base::ParserError, fmt::format("Expected ')', got '{}'", input[pos]));
|
||||
}
|
||||
return expr;
|
||||
}
|
||||
if (peekColor()) {
|
||||
return parseColor();
|
||||
}
|
||||
if (peekParameter()) {
|
||||
return parseParameter();
|
||||
}
|
||||
if (peekFunction()) {
|
||||
return parseFunctionCall();
|
||||
}
|
||||
return parseNumber();
|
||||
}
|
||||
|
||||
bool Parser::peekColor()
|
||||
{
|
||||
skipWhitespace();
|
||||
// clang-format off
|
||||
return input[pos] == '#'
|
||||
|| peekString(rgbFunction)
|
||||
|| peekString(rgbaFunction);
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parseColor()
|
||||
{
|
||||
const auto parseHexadecimalColor = [&]() {
|
||||
constexpr int hexadecimalBase = 16;
|
||||
|
||||
// Format is #RRGGBB
|
||||
pos++;
|
||||
int r = std::stoi(input.substr(pos, 2), nullptr, hexadecimalBase);
|
||||
pos += 2;
|
||||
int g = std::stoi(input.substr(pos, 2), nullptr, hexadecimalBase);
|
||||
pos += 2;
|
||||
int b = std::stoi(input.substr(pos, 2), nullptr, hexadecimalBase);
|
||||
pos += 2;
|
||||
|
||||
return std::make_unique<Color>(QColor(r, g, b));
|
||||
};
|
||||
|
||||
const auto parseFunctionStyleColor = [&]() {
|
||||
bool hasAlpha = peekString(rgbaFunction);
|
||||
|
||||
pos += hasAlpha ? strlen(rgbaFunction) : strlen(rgbFunction);
|
||||
|
||||
int r = parseInt();
|
||||
if (!match(',')) {
|
||||
THROWM(Base::ParserError, fmt::format("Expected ',' after red, got '{}'", input[pos]));
|
||||
}
|
||||
int g = parseInt();
|
||||
if (!match(',')) {
|
||||
THROWM(Base::ParserError, fmt::format("Expected ',' after green, got '{}'", input[pos]));
|
||||
}
|
||||
int b = parseInt();
|
||||
int a = 255; // NOLINT(*-magic-numbers)
|
||||
if (hasAlpha) {
|
||||
if (!match(',')) {
|
||||
THROWM(Base::ParserError, fmt::format("Expected ',' after blue, got '{}'", input[pos]));
|
||||
}
|
||||
a = parseInt();
|
||||
}
|
||||
if (!match(')')) {
|
||||
THROWM(Base::ParserError, fmt::format("Expected ')' after color arguments, got '{}'", input[pos]));
|
||||
}
|
||||
return std::make_unique<Color>(QColor(r, g, b, a));
|
||||
};
|
||||
|
||||
skipWhitespace();
|
||||
|
||||
try {
|
||||
if (input[pos] == '#') {
|
||||
return parseHexadecimalColor();
|
||||
}
|
||||
|
||||
if (peekString(rgbFunction) || peekString(rgbaFunction)) {
|
||||
return parseFunctionStyleColor();
|
||||
}
|
||||
} catch (std::invalid_argument&) {
|
||||
THROWM(Base::ParserError, "Invalid color format, expected #RRGGBB or rgb(r,g,b) or rgba(r,g,b,a)");
|
||||
}
|
||||
|
||||
THROWM(Base::ParserError, "Unknown color format");
|
||||
}
|
||||
|
||||
bool Parser::peekParameter()
|
||||
{
|
||||
skipWhitespace();
|
||||
return pos < input.size() && input[pos] == '@';
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parseParameter()
|
||||
{
|
||||
skipWhitespace();
|
||||
if (!match('@')) {
|
||||
THROWM(Base::ParserError, fmt::format("Expected '@' for parameter, got '{}'", input[pos]));
|
||||
}
|
||||
size_t start = pos;
|
||||
while (pos < input.size() && (isalnum(input[pos]) || input[pos] == '_')) {
|
||||
++pos;
|
||||
}
|
||||
if (start == pos) {
|
||||
THROWM(Base::ParserError,
|
||||
fmt::format("Expected parameter name after '@', got '{}'", input[pos]));
|
||||
}
|
||||
return std::make_unique<ParameterReference>(input.substr(start, pos - start));
|
||||
}
|
||||
|
||||
bool Parser::peekFunction()
|
||||
{
|
||||
skipWhitespace();
|
||||
return pos < input.size() && isalpha(input[pos]);
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parseFunctionCall()
|
||||
{
|
||||
skipWhitespace();
|
||||
size_t start = pos;
|
||||
while (pos < input.size() && isalnum(input[pos])) {
|
||||
++pos;
|
||||
}
|
||||
std::string functionName = input.substr(start, pos - start);
|
||||
|
||||
if (!match('(')) {
|
||||
THROWM(Base::ParserError,
|
||||
fmt::format("Expected '(' after function name, got '{}'", input[pos]));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<Expr>> arguments;
|
||||
if (!match(')')) {
|
||||
do { // NOLINT(*-avoid-do-while)
|
||||
arguments.push_back(parseExpression());
|
||||
} while (match(','));
|
||||
|
||||
if (!match(')')) {
|
||||
THROWM(Base::ParserError,
|
||||
fmt::format("Expected ')' after function arguments, got '{}'", input[pos]));
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_unique<FunctionCall>(functionName, std::move(arguments));
|
||||
}
|
||||
|
||||
int Parser::parseInt()
|
||||
{
|
||||
skipWhitespace();
|
||||
size_t start = pos;
|
||||
while (pos < input.size() && (isdigit(input[pos]) || input[pos] == '.')) {
|
||||
++pos;
|
||||
}
|
||||
return std::stoi(input.substr(start, pos - start));
|
||||
}
|
||||
|
||||
std::unique_ptr<Expr> Parser::parseNumber()
|
||||
{
|
||||
skipWhitespace();
|
||||
size_t start = pos;
|
||||
while (pos < input.size() && (isdigit(input[pos]) || input[pos] == '.')) {
|
||||
++pos;
|
||||
}
|
||||
|
||||
std::string number = input.substr(start, pos - start);
|
||||
|
||||
try {
|
||||
double value = std::stod(number);
|
||||
std::string unit = parseUnit();
|
||||
return std::make_unique<Number>(value, unit);
|
||||
}
|
||||
catch (std::invalid_argument& e) {
|
||||
THROWM(Base::ParserError, fmt::format("Invalid number: {}", number));
|
||||
}
|
||||
}
|
||||
|
||||
std::string Parser::parseUnit()
|
||||
{
|
||||
skipWhitespace();
|
||||
size_t start = pos;
|
||||
while (pos < input.size() && (isalpha(input[pos]) || input[pos] == '%')) {
|
||||
++pos;
|
||||
}
|
||||
if (start == pos) {
|
||||
return "";
|
||||
}
|
||||
return input.substr(start, pos - start);
|
||||
}
|
||||
|
||||
bool Parser::match(char expected)
|
||||
{
|
||||
skipWhitespace();
|
||||
if (pos < input.size() && input[pos] == expected) {
|
||||
++pos;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Parser::skipWhitespace()
|
||||
{
|
||||
while (pos < input.size() && isspace(input[pos])) {
|
||||
++pos;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Gui::StyleParameters
|
||||
@@ -0,0 +1,170 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <[email protected]> *
|
||||
* *
|
||||
* 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 STYLEPARAMETERS_PARSER_H
|
||||
#define STYLEPARAMETERS_PARSER_H
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "ParameterManager.h"
|
||||
|
||||
namespace Gui::StyleParameters
|
||||
{
|
||||
|
||||
enum class Operator : std::uint8_t
|
||||
{
|
||||
Add,
|
||||
Subtract,
|
||||
Multiply,
|
||||
Divide
|
||||
};
|
||||
|
||||
struct EvaluationContext
|
||||
{
|
||||
const ParameterManager* manager {};
|
||||
ParameterManager::ResolveContext context;
|
||||
};
|
||||
|
||||
// Abstract Syntax Tree (AST) Base
|
||||
struct GuiExport Expr
|
||||
{
|
||||
Expr() = default;
|
||||
|
||||
FC_DEFAULT_MOVE(Expr);
|
||||
FC_DISABLE_COPY(Expr);
|
||||
|
||||
virtual Value evaluate(const EvaluationContext& context) const = 0;
|
||||
virtual ~Expr() = default;
|
||||
};
|
||||
|
||||
struct GuiExport ParameterReference: public Expr
|
||||
{
|
||||
std::string name;
|
||||
|
||||
explicit ParameterReference(std::string name)
|
||||
: name(std::move(name))
|
||||
{}
|
||||
|
||||
Value evaluate(const EvaluationContext& context) const override;
|
||||
};
|
||||
|
||||
struct GuiExport Number: public Expr
|
||||
{
|
||||
Length value;
|
||||
|
||||
Number(double value, std::string unit)
|
||||
: value({value, std::move(unit)})
|
||||
{}
|
||||
|
||||
Value evaluate([[maybe_unused]] const EvaluationContext& context) const override;
|
||||
};
|
||||
|
||||
struct GuiExport Color: public Expr
|
||||
{
|
||||
QColor color;
|
||||
|
||||
explicit Color(QColor color)
|
||||
: color(std::move(color))
|
||||
{}
|
||||
|
||||
Value evaluate([[maybe_unused]] const EvaluationContext& context) const override;
|
||||
};
|
||||
|
||||
struct GuiExport FunctionCall: public Expr
|
||||
{
|
||||
std::string functionName;
|
||||
std::vector<std::unique_ptr<Expr>> arguments;
|
||||
|
||||
FunctionCall(std::string functionName, std::vector<std::unique_ptr<Expr>> arguments)
|
||||
: functionName(std::move(functionName))
|
||||
, arguments(std::move(arguments))
|
||||
{}
|
||||
|
||||
Value evaluate(const EvaluationContext& context) const override;
|
||||
};
|
||||
|
||||
struct GuiExport BinaryOp: public Expr
|
||||
{
|
||||
std::unique_ptr<Expr> left, right;
|
||||
Operator op;
|
||||
|
||||
BinaryOp(std::unique_ptr<Expr> left, Operator op, std::unique_ptr<Expr> right)
|
||||
: left(std::move(left))
|
||||
, right(std::move(right))
|
||||
, op(op)
|
||||
{}
|
||||
|
||||
Value evaluate(const EvaluationContext& context) const override;
|
||||
};
|
||||
|
||||
struct GuiExport UnaryOp: public Expr
|
||||
{
|
||||
Operator op;
|
||||
std::unique_ptr<Expr> operand;
|
||||
|
||||
UnaryOp(Operator op, std::unique_ptr<Expr> operand)
|
||||
: op(op)
|
||||
, operand(std::move(operand))
|
||||
{}
|
||||
|
||||
Value evaluate(const EvaluationContext& context) const override;
|
||||
};
|
||||
|
||||
class GuiExport Parser
|
||||
{
|
||||
static constexpr auto rgbFunction = "rgb(";
|
||||
static constexpr auto rgbaFunction = "rgba(";
|
||||
|
||||
std::string input;
|
||||
size_t pos = 0;
|
||||
|
||||
public:
|
||||
explicit Parser(std::string input)
|
||||
: input(std::move(input))
|
||||
{}
|
||||
|
||||
std::unique_ptr<Expr> parse();
|
||||
|
||||
private:
|
||||
bool peekString(const char* function) const;
|
||||
std::unique_ptr<Expr> parseExpression();
|
||||
std::unique_ptr<Expr> parseTerm();
|
||||
std::unique_ptr<Expr> parseFactor();
|
||||
bool peekColor();
|
||||
std::unique_ptr<Expr> parseColor();
|
||||
bool peekParameter();
|
||||
std::unique_ptr<Expr> parseParameter();
|
||||
bool peekFunction();
|
||||
std::unique_ptr<Expr> parseFunctionCall();
|
||||
int parseInt();
|
||||
std::unique_ptr<Expr> parseNumber();
|
||||
std::string parseUnit();
|
||||
bool match(char expected);
|
||||
void skipWhitespace();
|
||||
};
|
||||
|
||||
} // namespace Gui::StyleParameters
|
||||
|
||||
#endif // STYLEPARAMETERS_PARSER_H
|
||||
+86
-24
@@ -1,5 +1,6 @@
|
||||
/***************************************************************************
|
||||
* Copyright (c) 2020 Werner Mayer <wmayer[at]users.sourceforge.net> *
|
||||
* Copyright (c) 2025 Kacper Donat <[email protected]> *
|
||||
* *
|
||||
* This file is part of the FreeCAD CAx development system. *
|
||||
* *
|
||||
@@ -23,39 +24,100 @@
|
||||
#ifndef GUI_TOOLS_H
|
||||
#define GUI_TOOLS_H
|
||||
|
||||
#include <FCGlobal.h>
|
||||
|
||||
#include <QFontMetrics>
|
||||
#include <QKeyEvent>
|
||||
#include <QKeySequence>
|
||||
#include <FCGlobal.h>
|
||||
#include <QModelIndex>
|
||||
|
||||
namespace Gui {
|
||||
|
||||
/*!
|
||||
* \brief The QtTools class
|
||||
* Helper class to reduce adding a lot of extra QT_VERSION checks to client code.
|
||||
/**
|
||||
* @brief The QtTools namespace
|
||||
*
|
||||
* Helper namespace to provide utilities to ease work with Qt.
|
||||
*/
|
||||
class GuiExport QtTools {
|
||||
public:
|
||||
static int horizontalAdvance(const QFontMetrics& fm, QChar ch) {
|
||||
namespace Gui::QtTools
|
||||
{
|
||||
inline int horizontalAdvance(const QFontMetrics& fm, QChar ch)
|
||||
{
|
||||
return fm.horizontalAdvance(ch);
|
||||
}
|
||||
|
||||
return fm.horizontalAdvance(ch);
|
||||
}
|
||||
static int horizontalAdvance(const QFontMetrics& fm, const QString& text, int len = -1) {
|
||||
return fm.horizontalAdvance(text, len);
|
||||
}
|
||||
static bool matches(QKeyEvent* ke, const QKeySequence& ks) {
|
||||
uint searchkey = (ke->modifiers() | ke->key()) & ~(Qt::KeypadModifier | Qt::GroupSwitchModifier);
|
||||
return ks == QKeySequence(searchkey);
|
||||
}
|
||||
static QKeySequence::StandardKey deleteKeySequence() {
|
||||
inline int horizontalAdvance(const QFontMetrics& fm, const QString& text, int len = -1)
|
||||
{
|
||||
return fm.horizontalAdvance(text, len);
|
||||
}
|
||||
|
||||
inline bool matches(QKeyEvent* ke, const QKeySequence& ks)
|
||||
{
|
||||
uint searchkey =
|
||||
(ke->modifiers() | ke->key()) & ~(Qt::KeypadModifier | Qt::GroupSwitchModifier);
|
||||
return ks == QKeySequence(searchkey);
|
||||
}
|
||||
|
||||
inline QKeySequence::StandardKey deleteKeySequence() {
|
||||
#ifdef FC_OS_MACOSX
|
||||
return QKeySequence::Backspace;
|
||||
return QKeySequence::Backspace;
|
||||
#else
|
||||
return QKeySequence::Delete;
|
||||
return QKeySequence::Delete;
|
||||
#endif
|
||||
}
|
||||
|
||||
// clang-format off
|
||||
/**
|
||||
* TreeWalkCallable is a function that takes const QModelIndex& and:
|
||||
* - returns void, if there is no stopping logic;
|
||||
* - returns boolean, if there is logic that should stop tree traversal.
|
||||
*/
|
||||
template<typename Func>
|
||||
concept TreeWalkCallable =
|
||||
std::is_invocable_r_v<void, Func, const QModelIndex&> ||
|
||||
std::is_invocable_r_v<bool, Func, const QModelIndex&>;
|
||||
// clang-format on
|
||||
|
||||
/**
|
||||
* @brief Recursively traverses a QAbstractItemModel tree structure.
|
||||
*
|
||||
* The function traverses a tree model starting from a given index, or the root
|
||||
* if no index is provided. For each node, it invokes the provided callable `func`.
|
||||
*
|
||||
* The callable can:
|
||||
* - Return `void`, in which case the traversal continues through all nodes.
|
||||
* - Return `bool`, in which case returning `true` stops further traversal.
|
||||
*
|
||||
* @param[in] model The tree model to traverse.
|
||||
* @param[in] func A callable object applied to each QModelIndex. It can either
|
||||
* return `void` or `bool` (for stopping logic).
|
||||
* @param[in] index The starting index for traversal. If omitted, defaults to the root.
|
||||
*/
|
||||
void walkTreeModel(const QAbstractItemModel* model,
|
||||
TreeWalkCallable auto&& func,
|
||||
const QModelIndex& index = {})
|
||||
{
|
||||
using ReturnType = std::invoke_result_t<decltype(func), const QModelIndex&>;
|
||||
|
||||
if (index.isValid()) {
|
||||
if constexpr (std::is_same_v<ReturnType, void>) {
|
||||
func(index);
|
||||
}
|
||||
else if constexpr (std::is_same_v<ReturnType, bool>) {
|
||||
if (func(index)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Gui
|
||||
for (int i = 0; i < model->rowCount(index); ++i) {
|
||||
walkTreeModel(model, func, model->index(i, 0, index));
|
||||
}
|
||||
}
|
||||
|
||||
#endif // GUI_TOOLS_H
|
||||
template<typename T>
|
||||
T valueOr(const QVariant& variant, const T& defaultValue)
|
||||
{
|
||||
return variant.canConvert<T>() ? variant.value<T>() : defaultValue;
|
||||
}
|
||||
|
||||
} // namespace Gui::QtTools
|
||||
|
||||
#endif // GUI_TOOLS_H
|
||||
|
||||
@@ -673,8 +673,11 @@ public:
|
||||
|
||||
QIcon getIcon(QPixmap px) {
|
||||
static int iconSize = -1;
|
||||
if(iconSize < 0)
|
||||
iconSize = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon).width();
|
||||
if (iconSize < 0) {
|
||||
auto sampleIcon = QApplication::style()->standardPixmap(QStyle::SP_DirClosedIcon);
|
||||
double pixelRatio = sampleIcon.devicePixelRatio();
|
||||
iconSize = static_cast<int>(sampleIcon.width() / pixelRatio);
|
||||
}
|
||||
|
||||
if(!isLinked())
|
||||
return QIcon();
|
||||
|
||||
@@ -767,11 +767,11 @@ MenuItem* StdWorkbench::setupMenuBar() const
|
||||
// Help
|
||||
auto help = new MenuItem( menuBar );
|
||||
help->setCommand("&Help");
|
||||
*help << "Std_OnlineHelp" << "Std_WhatsThis" << "Separator"
|
||||
*help << "Std_WhatsThis" << "Separator"
|
||||
// Start page and additional separator are dynamically inserted here
|
||||
<< "Std_FreeCADUserHub" << "Std_FreeCADForum" << "Std_FreeCADFAQ" << "Std_ReportBug" << "Separator"
|
||||
<< "Std_FreeCADUserHub" << "Std_FreeCADForum" << "Std_ReportBug" << "Separator"
|
||||
<< "Std_RestartInSafeMode" << "Separator"
|
||||
<< "Std_FreeCADPowerUserHub" << "Std_PythonHelp" << "Separator"
|
||||
<< "Std_DevHandbook" << "Std_PythonHelp" << "Separator"
|
||||
<< "Std_FreeCADWebsite" << "Std_FreeCADDonation" << "Std_About";
|
||||
|
||||
return menuBar;
|
||||
|
||||
+44
-11
@@ -794,7 +794,12 @@ def makePipeConnector(pipes, radius=0, name=None):
|
||||
|
||||
# Initialize all relevant properties
|
||||
pipeConnector.Pipes = pipes
|
||||
pipeConnector.Radius = radius if radius else pipes[0].Diameter
|
||||
if radius:
|
||||
pipeConnector.Radius = radius
|
||||
elif pipes[0].ProfileType == "Circle":
|
||||
pipeConnector.Radius = pipes[0].Diameter
|
||||
else:
|
||||
pipeConnector.Radius = max(pipes[0].Height, pipes[0].Width)
|
||||
|
||||
return pipeConnector
|
||||
|
||||
@@ -1686,7 +1691,7 @@ def makeWall(
|
||||
return wall
|
||||
|
||||
|
||||
def joinWalls(walls, delete=False):
|
||||
def joinWalls(walls, delete=False, deletebase=False):
|
||||
"""Join the given list of walls into one sketch-based wall.
|
||||
|
||||
Take the first wall in the list, and adds on the other walls in the list.
|
||||
@@ -1702,6 +1707,9 @@ def joinWalls(walls, delete=False):
|
||||
be based off a base object.
|
||||
delete : bool, optional
|
||||
If True, deletes the other walls in the list. Defaults to False.
|
||||
deletebase : bool, optional
|
||||
If True, and delete is True, the base of the other walls is also deleted
|
||||
Defaults to False.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -1740,14 +1748,39 @@ def joinWalls(walls, delete=False):
|
||||
else:
|
||||
sk = base.Base
|
||||
for w in walls:
|
||||
if w.Base:
|
||||
if not w.Base.Shape.Faces:
|
||||
for e in w.Base.Shape.Edges:
|
||||
l = e.Curve
|
||||
if isinstance(l, Part.Line):
|
||||
l = Part.LineSegment(e.Vertexes[0].Point, e.Vertexes[-1].Point)
|
||||
sk.addGeometry(l)
|
||||
deleteList.append(w.Name)
|
||||
if w.Base and not w.Base.Shape.Faces:
|
||||
for hostedObj in w.Proxy.getHosts(w):
|
||||
if hasattr(hostedObj, "Host"):
|
||||
hostedObj.Host = base
|
||||
else:
|
||||
tmp = hostedObj.Hosts
|
||||
if delete:
|
||||
tmp.remove(w)
|
||||
if not base in tmp:
|
||||
tmp.append(base)
|
||||
hostedObj.Hosts = tmp
|
||||
tmp = []
|
||||
for add in w.Additions:
|
||||
if not add in base.Additions:
|
||||
tmp.append(add)
|
||||
if delete:
|
||||
w.Additions = None
|
||||
base.Additions += tmp
|
||||
tmp = []
|
||||
for sub in w.Subtractions:
|
||||
if not sub in base.Subtractions:
|
||||
tmp.append(sub)
|
||||
if delete:
|
||||
w.Subtractions = None
|
||||
base.Subtractions += tmp
|
||||
for e in w.Base.Shape.Edges:
|
||||
l = e.Curve
|
||||
if isinstance(l, Part.Line):
|
||||
l = Part.LineSegment(e.Vertexes[0].Point, e.Vertexes[-1].Point)
|
||||
sk.addGeometry(l)
|
||||
deleteList.append(w.Name)
|
||||
if deletebase:
|
||||
deleteList.append(w.Base.Name)
|
||||
if delete:
|
||||
for n in deleteList:
|
||||
FreeCAD.ActiveDocument.removeObject(n)
|
||||
@@ -1933,4 +1966,4 @@ def _initializeArchObject(
|
||||
FreeCAD.Console.PrintError(f"Failed to import module '{moduleName}': {e}\n")
|
||||
return None
|
||||
|
||||
return obj
|
||||
return obj
|
||||
|
||||
+38
-32
@@ -93,6 +93,14 @@ def getDefaultColor(objectType):
|
||||
r, g, b, _ = Draft.get_rgba_tuple(c)
|
||||
return (r, g, b, alpha)
|
||||
|
||||
def _usedForAttachment(host,obj):
|
||||
if not getattr(obj,"AttachmentSupport",[]):
|
||||
return False
|
||||
for sub in obj.AttachmentSupport:
|
||||
if sub[0] == host:
|
||||
return True
|
||||
return False
|
||||
|
||||
def addComponents(objectsList,host):
|
||||
'''addComponents(objectsList,hostObject): adds the given object or the objects
|
||||
from the given list as components to the given host Object. Use this for
|
||||
@@ -105,24 +113,32 @@ def addComponents(objectsList,host):
|
||||
host.addObject(o)
|
||||
elif hostType in ["Wall","CurtainWall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component","Pipe"]:
|
||||
import DraftGeomUtils
|
||||
outList = host.OutListRecursive
|
||||
a = host.Additions
|
||||
if hasattr(host,"Axes"):
|
||||
x = host.Axes
|
||||
x = getattr(host,"Axes",[])
|
||||
for o in objectsList:
|
||||
if hasattr(o,'Shape'):
|
||||
if hasattr(o,"Shape"):
|
||||
if Draft.getType(o) == "Window":
|
||||
if hasattr(o,"Hosts"):
|
||||
if not host in o.Hosts:
|
||||
g = o.Hosts
|
||||
g.append(host)
|
||||
o.Hosts = g
|
||||
elif DraftGeomUtils.isValidPath(o.Shape) and (hostType in ["Structure","Precast"]):
|
||||
if o.AttachmentSupport == host:
|
||||
o.AttachmentSupport = None
|
||||
host.Tool = o
|
||||
elif o in outList:
|
||||
FreeCAD.Console.PrintWarning(
|
||||
translate(
|
||||
"Arch",
|
||||
"Cannot add {0} as it is already referenced by {1}."
|
||||
).format(o.Label, host.Label) + "\n"
|
||||
)
|
||||
elif Draft.getType(o) == "Axis":
|
||||
if not o in x:
|
||||
x.append(o)
|
||||
elif DraftGeomUtils.isValidPath(o.Shape) and (hostType in ["Structure","Precast"]):
|
||||
if _usedForAttachment(host,o):
|
||||
o.AttachmentSupport = None
|
||||
o.MapMode = "Deactivated"
|
||||
host.Tool = o
|
||||
elif not o in a:
|
||||
if hasattr(o,"Shape"):
|
||||
a.append(o)
|
||||
@@ -148,15 +164,15 @@ def removeComponents(objectsList,host=None):
|
||||
objectsList = [objectsList]
|
||||
if host:
|
||||
if Draft.getType(host) in ["Wall","CurtainWall","Structure","Precast","Window","Roof","Stairs","StructuralSystem","Panel","Component","Pipe"]:
|
||||
if hasattr(host,"Tool"):
|
||||
if objectsList[0] == host.Tool:
|
||||
host.Tool = None
|
||||
if getattr(host,"Tool",None) in objectsList:
|
||||
host.Tool = None
|
||||
if hasattr(host,"Axes"):
|
||||
a = host.Axes
|
||||
for o in objectsList[:]:
|
||||
if o in a:
|
||||
a.remove(o)
|
||||
objectsList.remove(o)
|
||||
host.Axes = a
|
||||
s = host.Subtractions
|
||||
for o in objectsList:
|
||||
if Draft.getType(o) == "Window":
|
||||
@@ -168,34 +184,24 @@ def removeComponents(objectsList,host=None):
|
||||
elif not o in s:
|
||||
s.append(o)
|
||||
if FreeCAD.GuiUp:
|
||||
if not Draft.getType(o) in ["Window","Roof"]:
|
||||
if Draft.getType(o) != "Roof":
|
||||
setAsSubcomponent(o)
|
||||
# Making reference to BimWindow.Arch_Window:
|
||||
# Check if o and o.Base has Attachment Support, and
|
||||
# if the support is the host object itself - thus a cyclic
|
||||
# dependency and probably creating TNP.
|
||||
# If above is positive, remove its AttachmentSupport:
|
||||
# Avoid cyclic dependency via Attachment Support:
|
||||
if hasattr(o,"Base") and o.Base:
|
||||
objList = [o, o.Base]
|
||||
else:
|
||||
objList = [o]
|
||||
for i in objList:
|
||||
objHost = None
|
||||
if hasattr(i,"AttachmentSupport"):
|
||||
if i.AttachmentSupport:
|
||||
if isinstance(i.AttachmentSupport,tuple):
|
||||
objHost = i.AttachmentSupport[0]
|
||||
elif isinstance(i.AttachmentSupport,list):
|
||||
objHost = i.AttachmentSupport[0][0]
|
||||
else:
|
||||
objHost = i.AttachmentSupport
|
||||
if objHost == host:
|
||||
msg = FreeCAD.Console.PrintMessage
|
||||
msg(i.Label + " is mapped to " + host.Label +
|
||||
", removing the former's Attachment " +
|
||||
"Support to avoid cyclic dependency and " +
|
||||
"TNP." + "\n")
|
||||
i.AttachmentSupport = None # remove
|
||||
if _usedForAttachment(host,i):
|
||||
FreeCAD.Console.PrintMessage(
|
||||
translate(
|
||||
"Arch",
|
||||
"{0} is mapped to {1}, removing the former's " +
|
||||
"Attachment Support to avoid cyclic dependency."
|
||||
).format(o.Label, host.Label) + "\n"
|
||||
)
|
||||
i.AttachmentSupport = None
|
||||
i.MapMode = "Deactivated"
|
||||
host.Subtractions = s
|
||||
elif Draft.getType(host) in ["SectionPlane"]:
|
||||
a = host.Objects
|
||||
|
||||
@@ -782,7 +782,7 @@ class Component(ArchIFC.IfcProduct):
|
||||
subvolume = o.getLinkedObject().Proxy.getSubVolume(o,host=obj) # pass host obj (mostly Wall)
|
||||
elif (Draft.getType(o) == "Roof") or (Draft.isClone(o,"Roof")):
|
||||
# roofs define their own special subtraction volume
|
||||
subvolume = o.Proxy.getSubVolume(o)
|
||||
subvolume = o.Proxy.getSubVolume(o).copy()
|
||||
elif hasattr(o,"Subvolume") and hasattr(o.Subvolume,"Shape"):
|
||||
# Any other object with a Subvolume property
|
||||
## TODO - Part.Shape() instead?
|
||||
@@ -1413,18 +1413,13 @@ class ViewProviderComponent:
|
||||
The name of the property that has changed.
|
||||
"""
|
||||
|
||||
#print(vobj.Object.Name, " : changing ",prop)
|
||||
#if prop == "Visibility":
|
||||
#for obj in vobj.Object.Additions+vobj.Object.Subtractions:
|
||||
# if (Draft.getType(obj) == "Window") or (Draft.isClone(obj,"Window",True)):
|
||||
# obj.ViewObject.Visibility = vobj.Visibility
|
||||
# this would now hide all previous windows... Not the desired behaviour anymore.
|
||||
obj = vobj.Object
|
||||
if prop == "DiffuseColor":
|
||||
if hasattr(vobj.Object,"CloneOf"):
|
||||
if vobj.Object.CloneOf and hasattr(vobj.Object.CloneOf,"DiffuseColor"):
|
||||
if len(vobj.Object.CloneOf.ViewObject.DiffuseColor) > 1:
|
||||
if vobj.DiffuseColor != vobj.Object.CloneOf.ViewObject.DiffuseColor:
|
||||
vobj.DiffuseColor = vobj.Object.CloneOf.ViewObject.DiffuseColor
|
||||
if hasattr(obj,"CloneOf"):
|
||||
if obj.CloneOf and hasattr(obj.CloneOf,"DiffuseColor"):
|
||||
if len(obj.CloneOf.ViewObject.DiffuseColor) > 1:
|
||||
if vobj.DiffuseColor != obj.CloneOf.ViewObject.DiffuseColor:
|
||||
vobj.DiffuseColor = obj.CloneOf.ViewObject.DiffuseColor
|
||||
vobj.update()
|
||||
elif prop == "ShapeColor":
|
||||
# restore DiffuseColor after overridden by ShapeColor
|
||||
@@ -1433,10 +1428,16 @@ class ViewProviderComponent:
|
||||
d = vobj.DiffuseColor
|
||||
vobj.DiffuseColor = d
|
||||
elif prop == "Visibility":
|
||||
for host in vobj.Object.Proxy.getHosts(vobj.Object):
|
||||
if hasattr(host, 'ViewObject'):
|
||||
host.ViewObject.Visibility = vobj.Visibility
|
||||
|
||||
# do nothing if object is an addition
|
||||
if not [parent for parent in obj.InList if obj in getattr(parent, "Additions", [])]:
|
||||
hostedObjs = obj.Proxy.getHosts(obj)
|
||||
# add objects hosted by additions
|
||||
for addition in getattr(obj, "Additions", []):
|
||||
if hasattr(addition, "Proxy") and hasattr(addition.Proxy, "getHosts"):
|
||||
hostedObjs.extend(addition.Proxy.getHosts(addition))
|
||||
for hostedObj in hostedObjs:
|
||||
if hasattr(hostedObj, "ViewObject"):
|
||||
hostedObj.ViewObject.Visibility = vobj.Visibility
|
||||
return
|
||||
|
||||
def attach(self,vobj):
|
||||
|
||||
@@ -137,9 +137,12 @@ class _ViewProviderProject(ArchIFCView.IfcContextView):
|
||||
https://forum.freecad.org/viewtopic.php?f=10&t=74731
|
||||
"""
|
||||
|
||||
from pivy import coin
|
||||
from draftutils import gui_utils
|
||||
|
||||
if not hasattr(self, "displaymodes_cleaned"):
|
||||
if vobj.RootNode.getNumChildren() > 2:
|
||||
main_switch = vobj.RootNode.getChild(2) # The display mode switch.
|
||||
if vobj.RootNode.getNumChildren():
|
||||
main_switch = gui_utils.find_coin_node(vobj.RootNode, coin.SoSwitch) # The display mode switch.
|
||||
if main_switch is not None and main_switch.getNumChildren() == 4: # Check if all display modes are available.
|
||||
for node in tuple(main_switch.getChildren()):
|
||||
node.removeAllChildren()
|
||||
|
||||
@@ -1173,7 +1173,7 @@ class _ViewProviderSectionPlane:
|
||||
actionToggleCutview = QtGui.QAction(QtGui.QIcon(":/icons/Draft_Edit.svg"),
|
||||
translate("Arch", "Toggle Cutview"),
|
||||
menu)
|
||||
actionToggleCutview.triggered.connect(lambda f=self.toggleCutview, arg=vobj: f(arg))
|
||||
actionToggleCutview.triggered.connect(lambda: self.toggleCutview(vobj))
|
||||
menu.addAction(actionToggleCutview)
|
||||
|
||||
def edit(self):
|
||||
|
||||
+4
-11
@@ -978,19 +978,12 @@ class _ViewProviderSite:
|
||||
"""
|
||||
|
||||
from pivy import coin
|
||||
|
||||
def find_node(parent, nodetype):
|
||||
for i in range(parent.getNumChildren()):
|
||||
if isinstance(parent.getChild(i), nodetype):
|
||||
return parent.getChild(i)
|
||||
return None
|
||||
from draftutils import gui_utils
|
||||
|
||||
if not hasattr(self, "terrain_switches"):
|
||||
if vobj.RootNode.getNumChildren() > 2:
|
||||
main_switch = find_node(vobj.RootNode, coin.SoSwitch)
|
||||
if not main_switch:
|
||||
return
|
||||
if main_switch.getNumChildren() == 4: # Check if all display modes are available.
|
||||
if vobj.RootNode.getNumChildren():
|
||||
main_switch = gui_utils.find_coin_node(vobj.RootNode, coin.SoSwitch) # The display mode switch.
|
||||
if main_switch is not None and main_switch.getNumChildren() == 4: # Check if all display modes are available.
|
||||
self.terrain_switches = []
|
||||
for node in tuple(main_switch.getChildren()):
|
||||
new_switch = coin.SoSwitch()
|
||||
|
||||
@@ -627,10 +627,9 @@ class _CommandStructure:
|
||||
self.dents.form.hide()
|
||||
params.set_param_arch("StructurePreset",self.Profile)
|
||||
else:
|
||||
p=elt[0]-1 # Presets indexes are 1-based
|
||||
self.vLength.setText(FreeCAD.Units.Quantity(float(Presets[p][4]),FreeCAD.Units.Length).UserString)
|
||||
self.vWidth.setText(FreeCAD.Units.Quantity(float(Presets[p][5]),FreeCAD.Units.Length).UserString)
|
||||
self.Profile = Presets[p]
|
||||
self.vLength.setText(FreeCAD.Units.Quantity(float(elt[4]),FreeCAD.Units.Length).UserString)
|
||||
self.vWidth.setText(FreeCAD.Units.Quantity(float(elt[5]),FreeCAD.Units.Length).UserString)
|
||||
self.Profile = elt
|
||||
params.set_param_arch("StructurePreset",";".join([str(i) for i in self.Profile]))
|
||||
|
||||
def switchLH(self,bmode):
|
||||
|
||||
@@ -113,6 +113,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkLoadTypes">
|
||||
<property name="toolTip">
|
||||
<string>Preload IFC types that are connected to the objects. It is also possible to leave this setting disabled and double click later on the object to load the types</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preload types</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="checkLoadPsets">
|
||||
<property name="toolTip">
|
||||
|
||||
@@ -406,9 +406,6 @@
|
||||
<property name="toolTip">
|
||||
<string>The main use of this building</string>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QComboBox::AdjustToMinimumContentsLength</enum>
|
||||
</property>
|
||||
<property name="minimumContentsLength">
|
||||
<number>0</number>
|
||||
</property>
|
||||
|
||||
@@ -129,6 +129,22 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_9">
|
||||
<property name="toolTip">
|
||||
<string>Load all types automatically when opening an IFC file</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Preload types</string>
|
||||
</property>
|
||||
<property name="prefEntry" stdset="0">
|
||||
<cstring>LoadTypes</cstring>
|
||||
</property>
|
||||
<property name="prefPath" stdset="0">
|
||||
<cstring>Mod/NativeIFC</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="Gui::PrefCheckBox" name="checkBox_8">
|
||||
<property name="toolTip">
|
||||
|
||||
@@ -77,7 +77,7 @@ class Arch_Profile:
|
||||
# categories box
|
||||
labelc = QtGui.QLabel(translate("Arch","Category"))
|
||||
self.vCategory = QtGui.QComboBox()
|
||||
self.vCategory.addItems([" "] + self.Categories)
|
||||
self.vCategory.addItems(self.Categories)
|
||||
grid.addWidget(labelc,1,0,1,1)
|
||||
grid.addWidget(self.vCategory,1,1,1,1)
|
||||
|
||||
@@ -90,22 +90,32 @@ class Arch_Profile:
|
||||
grid.addWidget(labelp,2,0,1,1)
|
||||
grid.addWidget(self.vPresets,2,1,1,1)
|
||||
|
||||
# restore preset
|
||||
categoryIdx = -1
|
||||
presetIdx = -1
|
||||
stored = params.get_param_arch("ProfilePreset")
|
||||
if stored and ";" in stored and len(stored.split(";")) >= 3:
|
||||
stored = stored.split(";")
|
||||
if stored[1] in self.Categories:
|
||||
categoryIdx = self.Categories.index(stored[1])
|
||||
self.setCategory(categoryIdx)
|
||||
self.vCategory.setCurrentIndex(categoryIdx)
|
||||
ps = [p[2] for p in self.pSelect]
|
||||
if stored[2] in ps:
|
||||
presetIdx = ps.index(stored[2])
|
||||
self.setPreset(presetIdx)
|
||||
self.vPresets.setCurrentIndex(presetIdx)
|
||||
if categoryIdx == -1:
|
||||
self.setCategory(0)
|
||||
self.vCategory.setCurrentIndex(0)
|
||||
if presetIdx == -1:
|
||||
self.setPreset(0)
|
||||
self.vPresets.setCurrentIndex(0)
|
||||
|
||||
# connect slots
|
||||
self.vCategory.currentIndexChanged.connect(self.setCategory)
|
||||
self.vPresets.currentIndexChanged.connect(self.setPreset)
|
||||
|
||||
# restore preset
|
||||
stored = params.get_param_arch("StructurePreset")
|
||||
if stored:
|
||||
if ";" in stored:
|
||||
stored = stored.split(";")
|
||||
if len(stored) >= 3:
|
||||
if stored[1] in self.Categories:
|
||||
self.vCategory.setCurrentIndex(1+self.Categories.index(stored[1]))
|
||||
self.setCategory(1+self.Categories.index(stored[1]))
|
||||
ps = [p[2] for p in self.pSelect]
|
||||
if stored[2] in ps:
|
||||
self.vPresets.setCurrentIndex(ps.index(stored[2]))
|
||||
return w
|
||||
|
||||
def getPoint(self,point=None,obj=None):
|
||||
@@ -134,32 +144,23 @@ class Arch_Profile:
|
||||
|
||||
from draftutils import params
|
||||
self.vPresets.clear()
|
||||
if i == 0:
|
||||
self.pSelect = [None]
|
||||
self.vPresets.addItems([" "])
|
||||
params.set_param_arch("StructurePreset","")
|
||||
else:
|
||||
self.pSelect = [p for p in self.Presets if p[1] == self.Categories[i-1]]
|
||||
fpresets = []
|
||||
for p in self.pSelect:
|
||||
f = FreeCAD.Units.Quantity(p[4],FreeCAD.Units.Length).getUserPreferred()
|
||||
d = params.get_param("Decimals",path="Units")
|
||||
s1 = str(round(p[4]/f[1],d))
|
||||
s2 = str(round(p[5]/f[1],d))
|
||||
s3 = str(f[2])
|
||||
fpresets.append(p[2]+" ("+s1+"x"+s2+s3+")")
|
||||
self.vPresets.addItems(fpresets)
|
||||
self.setPreset(0)
|
||||
self.pSelect = [p for p in self.Presets if p[1] == self.Categories[i]]
|
||||
fpresets = []
|
||||
for p in self.pSelect:
|
||||
f = FreeCAD.Units.Quantity(p[4],FreeCAD.Units.Length).getUserPreferred()
|
||||
d = params.get_param("Decimals",path="Units")
|
||||
s1 = str(round(p[4]/f[1],d))
|
||||
s2 = str(round(p[5]/f[1],d))
|
||||
s3 = str(f[2])
|
||||
fpresets.append(p[2]+" ("+s1+"x"+s2+s3+")")
|
||||
self.vPresets.addItems(fpresets)
|
||||
self.setPreset(0)
|
||||
|
||||
def setPreset(self,i):
|
||||
|
||||
from draftutils import params
|
||||
self.Profile = None
|
||||
elt = self.pSelect[i]
|
||||
if elt:
|
||||
p=elt[0]-1 # Presets indexes are 1-based
|
||||
self.Profile = self.Presets[p]
|
||||
params.set_param_arch("StructurePreset",";".join([str(i) for i in self.Profile]))
|
||||
self.Profile = self.pSelect[i]
|
||||
params.set_param_arch("ProfilePreset",";".join([str(i) for i in self.Profile]))
|
||||
|
||||
|
||||
FreeCADGui.addCommand('Arch_Profile',Arch_Profile())
|
||||
|
||||
@@ -114,7 +114,7 @@ class Arch_Wall:
|
||||
self.tracker = DraftTrackers.boxTracker()
|
||||
FreeCADGui.Snapper.getPoint(callback=self.getPoint,
|
||||
extradlg=self.taskbox(),
|
||||
title=translate("Arch","First point of wall")+":")
|
||||
title=translate("Arch", "First point of wall"))
|
||||
FreeCADGui.draftToolBar.continueCmd.show()
|
||||
|
||||
def getPoint(self,point=None,obj=None):
|
||||
@@ -132,8 +132,6 @@ class Arch_Wall:
|
||||
"""
|
||||
|
||||
import Draft
|
||||
import Part
|
||||
import Arch
|
||||
import ArchWall
|
||||
from draftutils import gui_utils
|
||||
if obj:
|
||||
@@ -154,40 +152,36 @@ class Arch_Wall:
|
||||
callback=self.getPoint,
|
||||
movecallback=self.update,
|
||||
extradlg=self.taskbox(),
|
||||
title=translate("Arch","Next point")+":",mode="line")
|
||||
title=translate("Arch", "Next point"),
|
||||
mode="line")
|
||||
|
||||
elif len(self.points) == 2:
|
||||
FreeCAD.activeDraftCommand = None
|
||||
FreeCADGui.Snapper.off()
|
||||
self.tracker.off()
|
||||
l = Part.LineSegment(self.wp.get_local_coords(self.points[0]),
|
||||
self.wp.get_local_coords(self.points[1]))
|
||||
self.doc.openTransaction(translate("Arch","Create Wall"))
|
||||
FreeCADGui.addModule("Arch")
|
||||
FreeCADGui.doCommand('import Part')
|
||||
FreeCADGui.doCommand('trace=Part.LineSegment(FreeCAD.'+str(l.StartPoint)+',FreeCAD.'+str(l.EndPoint)+')')
|
||||
if not self.existing:
|
||||
# no existing wall snapped, just add a default wall
|
||||
self.addDefault()
|
||||
else:
|
||||
if self.JOIN_WALLS_SKETCHES:
|
||||
# join existing subwalls first if possible, then add the new one
|
||||
w = Arch.joinWalls(self.existing)
|
||||
if w:
|
||||
if ArchWall.areSameWallTypes([w,self]):
|
||||
FreeCADGui.doCommand('FreeCAD.ActiveDocument.'+w.Name+'.Base.addGeometry(trace)')
|
||||
else:
|
||||
# if not possible, add new wall as addition to the existing one
|
||||
self.addDefault()
|
||||
if self.AUTOJOIN:
|
||||
FreeCADGui.doCommand('Arch.addComponents(FreeCAD.ActiveDocument.'+self.doc.Objects[-1].Name+',FreeCAD.ActiveDocument.'+w.Name+')')
|
||||
else:
|
||||
self.addDefault()
|
||||
else:
|
||||
# add new wall as addition to the first existing one
|
||||
self.addDefault()
|
||||
if self.AUTOJOIN:
|
||||
FreeCADGui.doCommand('Arch.addComponents(FreeCAD.ActiveDocument.'+self.doc.Objects[-1].Name+',FreeCAD.ActiveDocument.'+self.existing[0].Name+')')
|
||||
|
||||
self.doc.openTransaction(translate("Arch", "Create Wall"))
|
||||
|
||||
# Some code in gui_utils.autogroup requires a wall shape to determine
|
||||
# the target group. We therefore need to create a wall first.
|
||||
self.addDefault()
|
||||
wall = self.doc.Objects[-1]
|
||||
wallGrp = wall.getParentGroup()
|
||||
|
||||
if (self.JOIN_WALLS_SKETCHES or self.AUTOJOIN) \
|
||||
and self.existing \
|
||||
and self.existing[-1].getParentGroup() == wallGrp:
|
||||
oldWall = self.existing[-1]
|
||||
if self.JOIN_WALLS_SKETCHES and ArchWall.areSameWallTypes([wall, oldWall]):
|
||||
FreeCADGui.doCommand(
|
||||
"Arch.joinWalls([wall, doc." + oldWall.Name + "], "
|
||||
+ "delete=True, deletebase=True)"
|
||||
)
|
||||
elif self.AUTOJOIN:
|
||||
if wallGrp is not None:
|
||||
FreeCADGui.doCommand("wall.getParentGroup().removeObject(wall)")
|
||||
FreeCADGui.doCommand("Arch.addComponents(wall, doc." + oldWall.Name + ")")
|
||||
|
||||
self.doc.commitTransaction()
|
||||
self.doc.recompute()
|
||||
# gui_utils.end_all_events() # Causes a crash on Linux.
|
||||
@@ -200,36 +194,48 @@ class Arch_Wall:
|
||||
|
||||
Used solely by _CommandWall.getPoint() when the interactive mode has
|
||||
selected two points.
|
||||
|
||||
Relies on the assumption that FreeCADGui.doCommand() has already
|
||||
created a Part.LineSegment assigned as the variable "trace"
|
||||
"""
|
||||
|
||||
from draftutils import params
|
||||
|
||||
sta = self.wp.get_local_coords(self.points[0])
|
||||
end = self.wp.get_local_coords(self.points[1])
|
||||
|
||||
FreeCADGui.doCommand("import Part")
|
||||
FreeCADGui.addModule("Draft")
|
||||
FreeCADGui.addModule("Arch")
|
||||
FreeCADGui.addModule("WorkingPlane")
|
||||
FreeCADGui.doCommand("doc = FreeCAD.ActiveDocument")
|
||||
FreeCADGui.doCommand("wp = WorkingPlane.get_working_plane()")
|
||||
FreeCADGui.doCommand(
|
||||
"trace = Part.LineSegment(FreeCAD." + str(sta) + ", FreeCAD." + str(end) + ")"
|
||||
)
|
||||
if params.get_param_arch("WallSketches"):
|
||||
# Use ArchSketch if SketchArch add-on is present
|
||||
try:
|
||||
import ArchSketchObject
|
||||
FreeCADGui.doCommand('import ArchSketchObject')
|
||||
FreeCADGui.doCommand('base=ArchSketchObject.makeArchSketch()')
|
||||
FreeCADGui.doCommand("import ArchSketchObject")
|
||||
FreeCADGui.doCommand("base = ArchSketchObject.makeArchSketch()")
|
||||
except:
|
||||
FreeCADGui.doCommand('base=FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject","WallTrace")')
|
||||
FreeCADGui.doCommand('base.Placement = wp.get_placement()')
|
||||
FreeCADGui.doCommand('base.addGeometry(trace)')
|
||||
FreeCADGui.doCommand(
|
||||
"base = doc.addObject(\"Sketcher::SketchObject\", \"WallTrace\")"
|
||||
)
|
||||
FreeCADGui.doCommand("base.Placement = wp.get_placement()")
|
||||
FreeCADGui.doCommand("base.addGeometry(trace)")
|
||||
else:
|
||||
FreeCADGui.doCommand('base=Draft.make_line(trace)')
|
||||
FreeCADGui.doCommand("base = Draft.make_line(trace)")
|
||||
# The created line should not stay selected as this causes an issue in continue mode.
|
||||
# Two walls would then be created based on the same line.
|
||||
FreeCADGui.Selection.clearSelection()
|
||||
FreeCADGui.doCommand('base.Placement = wp.get_placement()')
|
||||
FreeCADGui.doCommand('FreeCAD.ActiveDocument.recompute()')
|
||||
FreeCADGui.doCommand('wall = Arch.makeWall(base,width='+str(self.Width)+',height='+str(self.Height)+',align="'+str(self.Align)+'")')
|
||||
FreeCADGui.doCommand('wall.Normal = wp.axis')
|
||||
FreeCADGui.doCommand("base.Placement = wp.get_placement()")
|
||||
FreeCADGui.doCommand("doc.recompute()")
|
||||
FreeCADGui.doCommand(
|
||||
"wall = Arch.makeWall(base, width=" + str(self.Width)
|
||||
+ ", height=" + str(self.Height) + ", align=\"" + str(self.Align) + "\")"
|
||||
)
|
||||
FreeCADGui.doCommand("wall.Normal = wp.axis")
|
||||
if self.MultiMat:
|
||||
FreeCADGui.doCommand("wall.Material = FreeCAD.ActiveDocument."+self.MultiMat.Name)
|
||||
FreeCADGui.doCommand("wall.Material = doc." + self.MultiMat.Name)
|
||||
FreeCADGui.doCommand("doc.recompute()") # required as some autogroup code requires the wall shape
|
||||
FreeCADGui.doCommand("Draft.autogroup(wall)")
|
||||
|
||||
def update(self,point,info):
|
||||
|
||||
@@ -32,6 +32,7 @@ from . import ifc_psets
|
||||
from . import ifc_materials
|
||||
from . import ifc_layers
|
||||
from . import ifc_status
|
||||
from . import ifc_types
|
||||
|
||||
if FreeCAD.GuiUp:
|
||||
import FreeCADGui
|
||||
@@ -101,6 +102,8 @@ def insert(
|
||||
ifc_layers.load_layers(prj_obj)
|
||||
if PARAMS.GetBool("LoadPsets", False):
|
||||
ifc_psets.load_psets(prj_obj)
|
||||
if PARAMS.GetBool("LoadTypes", False):
|
||||
ifc_types.load_types(prj_obj)
|
||||
document.recompute()
|
||||
# print a reference to the IFC file on the console
|
||||
if FreeCAD.GuiUp and PARAMS.GetBool("IfcFileToConsole", False):
|
||||
@@ -133,6 +136,7 @@ def get_options(strategy=None, shapemode=None, switchwb=None, silent=False):
|
||||
"""
|
||||
|
||||
psets = PARAMS.GetBool("LoadPsets", False)
|
||||
types = PARAMS.GetBool("LoadTypes", False)
|
||||
materials = PARAMS.GetBool("LoadMaterials", False)
|
||||
layers = PARAMS.GetBool("LoadLayers", False)
|
||||
singledoc = PARAMS.GetBool("SingleDoc", False)
|
||||
@@ -155,6 +159,7 @@ def get_options(strategy=None, shapemode=None, switchwb=None, silent=False):
|
||||
dlg.checkSwitchWB.setChecked(switchwb)
|
||||
dlg.checkAskAgain.setChecked(ask)
|
||||
dlg.checkLoadPsets.setChecked(psets)
|
||||
dlg.checkLoadTypes.setChecked(types)
|
||||
dlg.checkLoadMaterials.setChecked(materials)
|
||||
dlg.checkLoadLayers.setChecked(layers)
|
||||
dlg.comboSingleDoc.setCurrentIndex(1 - int(singledoc))
|
||||
@@ -166,6 +171,7 @@ def get_options(strategy=None, shapemode=None, switchwb=None, silent=False):
|
||||
switchwb = dlg.checkSwitchWB.isChecked()
|
||||
ask = dlg.checkAskAgain.isChecked()
|
||||
psets = dlg.checkLoadPsets.isChecked()
|
||||
types = dlg.checkLoadTypes.isChecked()
|
||||
materials = dlg.checkLoadMaterials.isChecked()
|
||||
layers = dlg.checkLoadLayers.isChecked()
|
||||
singledoc = dlg.comboSingleDoc.currentIndex()
|
||||
@@ -174,6 +180,7 @@ def get_options(strategy=None, shapemode=None, switchwb=None, silent=False):
|
||||
PARAMS.SetBool("SwitchWB", switchwb)
|
||||
PARAMS.SetBool("AskAgain", ask)
|
||||
PARAMS.SetBool("LoadPsets", psets)
|
||||
PARAMS.SetBool("LoadTypes", types)
|
||||
PARAMS.SetBool("LoadMaterials", materials)
|
||||
PARAMS.SetBool("LoadLayers", layers)
|
||||
PARAMS.SetBool("SingleDoc", bool(1 - singledoc))
|
||||
|
||||
@@ -350,6 +350,7 @@ def create_children(
|
||||
]
|
||||
for window in windows:
|
||||
subresult.extend(create_child(child, window))
|
||||
|
||||
if recursive:
|
||||
subresult.extend(
|
||||
create_children(
|
||||
|
||||
@@ -51,6 +51,34 @@ def show_type(obj):
|
||||
obj.Type = typeobj
|
||||
|
||||
|
||||
def load_types(prj_obj):
|
||||
"""
|
||||
Loads IFC types for all objects in the project, used during
|
||||
import of IFC files.
|
||||
prj_obj is the project object, either a document or a document object.
|
||||
"""
|
||||
|
||||
def process_object(obj):
|
||||
"""Recursively process an object and its children"""
|
||||
# Check if this object has IFC data and can have types
|
||||
if hasattr(obj, 'StepId') and obj.StepId:
|
||||
show_type(obj)
|
||||
|
||||
# Process children recursively
|
||||
if hasattr(obj, 'Group'):
|
||||
for child in obj.Group:
|
||||
process_object(child)
|
||||
|
||||
if isinstance(prj_obj, FreeCAD.DocumentObject):
|
||||
# Handle document object case
|
||||
process_object(prj_obj)
|
||||
else:
|
||||
# Handle document case - process all IFC objects in the document
|
||||
for obj in prj_obj.Objects:
|
||||
if hasattr(obj, 'StepId') and obj.StepId:
|
||||
show_type(obj)
|
||||
|
||||
|
||||
def is_typable(obj):
|
||||
"""Checks if an object can become a type"""
|
||||
|
||||
@@ -87,10 +115,9 @@ def convert_to_type(obj, keep_object=False):
|
||||
|
||||
original_text = dlg.label.text()
|
||||
dlg.label.setText(original_text.replace("%1", obj.Class+"Type"))
|
||||
|
||||
|
||||
# Set the initial state of the checkbox from the "always keep" preference
|
||||
dlg.checkKeepObject.setChecked(always_keep)
|
||||
|
||||
result = dlg.exec_()
|
||||
if not result:
|
||||
return
|
||||
|
||||
@@ -24,11 +24,10 @@ import FreeCAD
|
||||
import FreeCADGui
|
||||
import Path
|
||||
import Path.Op.Base as PathOp
|
||||
import PathScripts
|
||||
import PathScripts.PathUtils as PathUtils
|
||||
import Path.Base.Util as PathUtil
|
||||
from Path.Dressup.Utils import toolController
|
||||
from PySide import QtCore
|
||||
from PySide import QtGui
|
||||
|
||||
import random
|
||||
|
||||
@@ -154,8 +153,6 @@ class ObjectArray:
|
||||
self.setEditorModes(obj)
|
||||
obj.Proxy = self
|
||||
|
||||
self.FirstRun = True
|
||||
|
||||
def dumps(self):
|
||||
return None
|
||||
|
||||
@@ -223,42 +220,21 @@ class ObjectArray:
|
||||
|
||||
self.setEditorModes(obj)
|
||||
|
||||
self.FirstRun = True
|
||||
|
||||
def execute(self, obj):
|
||||
if FreeCAD.GuiUp and self.FirstRun:
|
||||
self.FirstRun = False
|
||||
QtGui.QMessageBox.warning(
|
||||
None,
|
||||
QT_TRANSLATE_NOOP("CAM_ArrayOp", "Operation is deprecated"),
|
||||
QT_TRANSLATE_NOOP(
|
||||
"CAM_ArrayOp",
|
||||
(
|
||||
"CAM -> Path Modification -> Array operation is deprecated "
|
||||
"and will be removed in future FreeCAD versions.\n\n"
|
||||
"Please use CAM -> Path Dressup -> Array instead.\n\n"
|
||||
"DO NOT USE CURRENT ARRAY OPERATION WHEN MACHINING WITH COOLANT!\n"
|
||||
"Due to a bug - coolant will not be enabled for array paths."
|
||||
),
|
||||
),
|
||||
)
|
||||
# backwards compatibility for PathArrays created before support for multiple bases
|
||||
if isinstance(obj.Base, list):
|
||||
base = obj.Base
|
||||
else:
|
||||
base = [obj.Base]
|
||||
|
||||
if len(base) == 0:
|
||||
# Do not generate paths and clear current Path data
|
||||
# if operation not Active or no base operations or operations not compatible
|
||||
if not obj.Active or len(base) == 0 or not self.isBaseCompatible(obj):
|
||||
obj.Path = Path.Path()
|
||||
return
|
||||
|
||||
obj.ToolController = toolController(base[0])
|
||||
|
||||
# Do not generate paths and clear current Path data if operation not
|
||||
if not obj.Active:
|
||||
if obj.Path:
|
||||
obj.Path = Path.Path()
|
||||
return
|
||||
|
||||
# use seed if specified, otherwise default to object name for consistency during recomputes
|
||||
seed = obj.JitterSeed or obj.Name
|
||||
|
||||
@@ -280,6 +256,37 @@ class ObjectArray:
|
||||
obj.Path = pa.getPath()
|
||||
obj.CycleTime = PathOp.getCycleTimeEstimate(obj)
|
||||
|
||||
def isBaseCompatible(self, obj):
|
||||
if not obj.Base:
|
||||
return False
|
||||
tcs = []
|
||||
cms = []
|
||||
for sel in obj.Base:
|
||||
if not sel.isDerivedFrom("Path::Feature"):
|
||||
return False
|
||||
tcs.append(toolController(sel))
|
||||
cms.append(PathUtil.coolantModeForOp(sel))
|
||||
|
||||
if tcs == {None} or len(set(tcs)) > 1:
|
||||
Path.Log.warning(
|
||||
translate(
|
||||
"PathArray",
|
||||
"Arrays of toolpaths having different tool controllers or tool controller not selected.",
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
if set(cms) != {"None"}:
|
||||
Path.Log.warning(
|
||||
translate(
|
||||
"PathArray",
|
||||
"Arrays not compatible with coolant modes.",
|
||||
)
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class PathArray:
|
||||
"""class PathArray ...
|
||||
@@ -337,10 +344,6 @@ class PathArray:
|
||||
"""getPath() ... Call this method on an instance of the class to generate and return
|
||||
path data for the requested path array."""
|
||||
|
||||
if len(self.baseList) == 0:
|
||||
Path.Log.error(translate("PathArray", "No base objects for PathArray."))
|
||||
return None
|
||||
|
||||
base = self.baseList
|
||||
for b in base:
|
||||
if not b.isDerivedFrom("Path::Feature"):
|
||||
@@ -352,15 +355,6 @@ class PathArray:
|
||||
if not b_tool_controller:
|
||||
return
|
||||
|
||||
if b_tool_controller != toolController(base[0]):
|
||||
# this may be important if Job output is split by tool controller
|
||||
Path.Log.warning(
|
||||
translate(
|
||||
"PathArray",
|
||||
"Arrays of toolpaths having different tool controllers are handled according to the tool controller of the first path.",
|
||||
)
|
||||
)
|
||||
|
||||
# build copies
|
||||
output = ""
|
||||
random.seed(self.seed)
|
||||
@@ -486,14 +480,32 @@ class CommandPathArray:
|
||||
return {
|
||||
"Pixmap": "CAM_Array",
|
||||
"MenuText": QT_TRANSLATE_NOOP("CAM_Array", "Array"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP("CAM_Array", "Creates an array from selected toolpath(s)"),
|
||||
"ToolTip": QT_TRANSLATE_NOOP(
|
||||
"CAM_Array",
|
||||
"Creates an array from selected toolpath(s)\nwith identical tool controllers and without coolant",
|
||||
),
|
||||
}
|
||||
|
||||
def IsActive(self):
|
||||
selections = [
|
||||
sel.isDerivedFrom("Path::Feature") for sel in FreeCADGui.Selection.getSelection()
|
||||
]
|
||||
return selections and all(selections)
|
||||
selection = FreeCADGui.Selection.getSelection()
|
||||
if not selection:
|
||||
return False
|
||||
tcs = []
|
||||
for sel in selection:
|
||||
if not sel.isDerivedFrom("Path::Feature"):
|
||||
return False
|
||||
tc = toolController(sel)
|
||||
if tc:
|
||||
# Active only for operations with identical tool controller
|
||||
tcs.append(tc)
|
||||
if len(set(tcs)) != 1:
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
if PathUtil.coolantModeForOp(sel) != "None":
|
||||
# Active only for operations without cooling
|
||||
return False
|
||||
return True
|
||||
|
||||
def Activated(self):
|
||||
|
||||
|
||||
@@ -78,11 +78,6 @@ def make_sketch(objects_list, autoconstraints=False, addTo=None,
|
||||
import Part
|
||||
from Sketcher import Constraint
|
||||
|
||||
if App.GuiUp:
|
||||
v_dir = gui_utils.get_3d_view().getViewDirection()
|
||||
else:
|
||||
v_dir = App.Vector(0, 0, -1)
|
||||
|
||||
# lists to accumulate shapes with defined normal and undefined normal
|
||||
shape_norm_yes = list()
|
||||
shape_norm_no = list()
|
||||
@@ -127,21 +122,20 @@ def make_sketch(objects_list, autoconstraints=False, addTo=None,
|
||||
# suppose all geometries are straight lines or points
|
||||
points = [vertex.Point for shape in shapes_list for vertex in shape.Vertexes]
|
||||
if len(points) >= 2:
|
||||
poly = Part.makePolygon(points)
|
||||
if not DraftGeomUtils.is_planar(poly, tol):
|
||||
App.Console.PrintError(translate("draft",
|
||||
"All Shapes must be coplanar") + "\n")
|
||||
return None
|
||||
normal = DraftGeomUtils.get_normal(poly, tol)
|
||||
if not normal:
|
||||
# all points aligned
|
||||
poly_dir = poly.Edges[0].Curve.Direction
|
||||
normal = (v_dir - v_dir.dot(poly_dir)*poly_dir).normalize()
|
||||
normal = normal.negative()
|
||||
try:
|
||||
poly = Part.makePolygon(points)
|
||||
except Part.OCCError:
|
||||
# all points coincide
|
||||
normal = App.Vector(0, 0, 1)
|
||||
else:
|
||||
if not DraftGeomUtils.is_planar(poly, tol):
|
||||
App.Console.PrintError(translate("draft",
|
||||
"All Shapes must be coplanar") + "\n")
|
||||
return None
|
||||
normal = DraftGeomUtils.get_shape_normal(poly)
|
||||
else:
|
||||
# only one point
|
||||
normal = v_dir.negative()
|
||||
|
||||
normal = App.Vector(0, 0, 1)
|
||||
|
||||
if addTo:
|
||||
nobj = addTo
|
||||
|
||||
@@ -120,71 +120,66 @@ def autogroup(obj):
|
||||
return
|
||||
|
||||
# autogroup code
|
||||
active_group = None
|
||||
if Gui.draftToolBar.autogroup is not None:
|
||||
active_group = App.ActiveDocument.getObject(Gui.draftToolBar.autogroup)
|
||||
if active_group:
|
||||
gr = active_group.Group
|
||||
if not obj in gr:
|
||||
gr.append(obj)
|
||||
active_group.Group = gr
|
||||
if obj in active_group.InListRecursive:
|
||||
return
|
||||
if not obj in active_group.Group:
|
||||
active_group.Group += [obj]
|
||||
|
||||
if Gui.ActiveDocument.ActiveView.getActiveObject("NativeIFC") is not None:
|
||||
elif Gui.ActiveDocument.ActiveView.getActiveObject("NativeIFC") is not None:
|
||||
# NativeIFC handling
|
||||
try:
|
||||
from nativeifc import ifc_tools
|
||||
parent = Gui.ActiveDocument.ActiveView.getActiveObject("NativeIFC")
|
||||
if parent != active_group:
|
||||
ifc_tools.aggregate(obj, parent)
|
||||
ifc_tools.aggregate(obj, parent)
|
||||
except:
|
||||
pass
|
||||
|
||||
elif Gui.ActiveDocument.ActiveView.getActiveObject("Arch") is not None:
|
||||
# add object to active Arch Container
|
||||
active_arch_obj = Gui.ActiveDocument.ActiveView.getActiveObject("Arch")
|
||||
if active_arch_obj != active_group:
|
||||
if obj in active_arch_obj.InListRecursive:
|
||||
# do not autogroup if obj points to active_arch_obj to prevent cyclic references
|
||||
return
|
||||
active_arch_obj.addObject(obj)
|
||||
if obj in active_arch_obj.InListRecursive:
|
||||
# do not autogroup if obj points to active_arch_obj to prevent cyclic references
|
||||
return
|
||||
active_arch_obj.addObject(obj)
|
||||
|
||||
elif Gui.ActiveDocument.ActiveView.getActiveObject("part") is not None:
|
||||
# add object to active part and change it's placement accordingly
|
||||
# so object does not jump to different position, works with App::Link
|
||||
# if not scaled. Modified accordingly to realthunder suggestions
|
||||
active_part, parent, sub = Gui.ActiveDocument.ActiveView.getActiveObject("part", False)
|
||||
if active_part != active_group:
|
||||
if obj in active_part.InListRecursive:
|
||||
# do not autogroup if obj points to active_part to prevent cyclic references
|
||||
return
|
||||
matrix = parent.getSubObject(sub, retType=4)
|
||||
if matrix.hasScale() == App.ScaleType.Uniform:
|
||||
err = translate("draft",
|
||||
"Unable to insert new object into "
|
||||
"a scaled part")
|
||||
App.Console.PrintMessage(err)
|
||||
return
|
||||
inverse_placement = App.Placement(matrix.inverse())
|
||||
if utils.get_type(obj) == 'Point':
|
||||
point_vector = App.Vector(obj.X, obj.Y, obj.Z)
|
||||
real_point = inverse_placement.multVec(point_vector)
|
||||
obj.X = real_point.x
|
||||
obj.Y = real_point.y
|
||||
obj.Z = real_point.z
|
||||
elif utils.get_type(obj) in ["Dimension", "LinearDimension"]:
|
||||
obj.Start = inverse_placement.multVec(obj.Start)
|
||||
obj.End = inverse_placement.multVec(obj.End)
|
||||
obj.Dimline = inverse_placement.multVec(obj.Dimline)
|
||||
obj.Normal = inverse_placement.Rotation.multVec(obj.Normal)
|
||||
obj.Direction = inverse_placement.Rotation.multVec(obj.Direction)
|
||||
elif utils.get_type(obj) in ["Label"]:
|
||||
obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement))
|
||||
obj.TargetPoint = inverse_placement.multVec(obj.TargetPoint)
|
||||
elif hasattr(obj,"Placement"):
|
||||
# every object that have a placement is processed here
|
||||
obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement))
|
||||
if obj in active_part.InListRecursive:
|
||||
# do not autogroup if obj points to active_part to prevent cyclic references
|
||||
return
|
||||
matrix = parent.getSubObject(sub, retType=4)
|
||||
if matrix.hasScale() == App.ScaleType.Uniform:
|
||||
err = translate("draft",
|
||||
"Unable to insert new object into "
|
||||
"a scaled part")
|
||||
App.Console.PrintMessage(err)
|
||||
return
|
||||
inverse_placement = App.Placement(matrix.inverse())
|
||||
if utils.get_type(obj) == 'Point':
|
||||
point_vector = App.Vector(obj.X, obj.Y, obj.Z)
|
||||
real_point = inverse_placement.multVec(point_vector)
|
||||
obj.X = real_point.x
|
||||
obj.Y = real_point.y
|
||||
obj.Z = real_point.z
|
||||
elif utils.get_type(obj) in ["Dimension", "LinearDimension"]:
|
||||
obj.Start = inverse_placement.multVec(obj.Start)
|
||||
obj.End = inverse_placement.multVec(obj.End)
|
||||
obj.Dimline = inverse_placement.multVec(obj.Dimline)
|
||||
obj.Normal = inverse_placement.Rotation.multVec(obj.Normal)
|
||||
obj.Direction = inverse_placement.Rotation.multVec(obj.Direction)
|
||||
elif utils.get_type(obj) in ["Label"]:
|
||||
obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement))
|
||||
obj.TargetPoint = inverse_placement.multVec(obj.TargetPoint)
|
||||
elif hasattr(obj,"Placement"):
|
||||
# every object that have a placement is processed here
|
||||
obj.Placement = App.Placement(inverse_placement.multiply(obj.Placement))
|
||||
|
||||
active_part.addObject(obj)
|
||||
active_part.addObject(obj)
|
||||
|
||||
|
||||
def dim_symbol(symbol=None, invert=False):
|
||||
|
||||
@@ -558,6 +558,7 @@ def _get_param_dictionary():
|
||||
"PrecastHoleSpacing": ("float", 0.0),
|
||||
"PrecastRiser": ("float", 0.0),
|
||||
"PrecastTread": ("float", 0.0),
|
||||
"ProfilePreset": ("string", ""),
|
||||
"ScheduleColumnWidth0": ("int", 100),
|
||||
"ScheduleColumnWidth1": ("int", 100),
|
||||
"ScheduleColumnWidth2": ("int", 50),
|
||||
|
||||
@@ -274,14 +274,14 @@
|
||||
<enum>QFormLayout::AllNonFixedFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="l_box_height">
|
||||
<widget class="QLabel" name="l_box_width">
|
||||
<property name="text">
|
||||
<string>Height</string>
|
||||
<string>Width</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="qsb_box_height">
|
||||
<widget class="Gui::QuantitySpinBox" name="qsb_box_width">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
@@ -306,14 +306,14 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="l_box_width">
|
||||
<widget class="QLabel" name="l_box_height">
|
||||
<property name="text">
|
||||
<string>Width</string>
|
||||
<string>Height</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="Gui::QuantitySpinBox" name="qsb_box_width">
|
||||
<widget class="Gui::QuantitySpinBox" name="qsb_box_height">
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeft|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
|
||||
@@ -177,7 +177,7 @@ Loft::Loft()
|
||||
{
|
||||
ADD_PROPERTY_TYPE(Sections, (nullptr), "Loft", App::Prop_None, "List of sections");
|
||||
Sections.setSize(0);
|
||||
ADD_PROPERTY_TYPE(Solid, (false), "Loft", App::Prop_None, "Create solid");
|
||||
ADD_PROPERTY_TYPE(Solid, (true), "Loft", App::Prop_None, "Create solid");
|
||||
ADD_PROPERTY_TYPE(Ruled, (false), "Loft", App::Prop_None, "Ruled surface");
|
||||
ADD_PROPERTY_TYPE(Closed, (false), "Loft", App::Prop_None, "Close Last to First Profile");
|
||||
ADD_PROPERTY_TYPE(MaxDegree, (5), "Loft", App::Prop_None, "Maximum Degree");
|
||||
@@ -257,7 +257,7 @@ Sweep::Sweep()
|
||||
ADD_PROPERTY_TYPE(Sections, (nullptr), "Sweep", App::Prop_None, "List of sections");
|
||||
Sections.setSize(0);
|
||||
ADD_PROPERTY_TYPE(Spine, (nullptr), "Sweep", App::Prop_None, "Path to sweep along");
|
||||
ADD_PROPERTY_TYPE(Solid, (false), "Sweep", App::Prop_None, "Create solid");
|
||||
ADD_PROPERTY_TYPE(Solid, (true), "Sweep", App::Prop_None, "Create solid");
|
||||
ADD_PROPERTY_TYPE(Frenet, (true), "Sweep", App::Prop_None, "Frenet");
|
||||
ADD_PROPERTY_TYPE(Transition, (long(1)), "Sweep", App::Prop_None, "Transition mode");
|
||||
ADD_PROPERTY_TYPE(Linearize,(false), "Sweep", App::Prop_None,
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
<property name="text">
|
||||
<string>Create solid</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
<property name="text">
|
||||
<string>Create solid</string>
|
||||
</property>
|
||||
<property name="checked">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
|
||||
@@ -253,6 +253,6 @@ PyMOD_INIT_FUNC(Sandbox)
|
||||
// the following constructor call registers our extension module
|
||||
// with the Python runtime system
|
||||
PyObject* mod = Sandbox::initModule();
|
||||
Base::Console().log("Loading Sandbox module... done\n");
|
||||
Base::Console().log("Loading Sandbox module… done\n");
|
||||
PyMOD_Return(mod);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ Py::Object DocumentProtectorPy::addObject(const Py::Tuple& args)
|
||||
if (!obj) {
|
||||
std::string s;
|
||||
std::ostringstream s_out;
|
||||
s_out << "Couldn't create an object of type '" << type << "'";
|
||||
s_out << "Could not create an object of type '" << type << "'";
|
||||
throw Py::RuntimeError(s_out.str());
|
||||
}
|
||||
//return Py::asObject(obj->getPyObject());
|
||||
|
||||
@@ -76,7 +76,7 @@ void WorkerThread::run()
|
||||
#else
|
||||
int max = 100000000;
|
||||
#endif
|
||||
Base::SequencerLauncher seq("Do something meaningful...", max);
|
||||
Base::SequencerLauncher seq("Do something meaningful…", max);
|
||||
double val=0;
|
||||
for (int i=0; i<max; i++) {
|
||||
for (int j=0; j<max; j++) {
|
||||
|
||||
@@ -215,6 +215,6 @@ PyMOD_INIT_FUNC(SandboxGui)
|
||||
// the following constructor call registers our extension module
|
||||
// with the Python runtime system
|
||||
PyObject* mod = SandboxGui::initModule();
|
||||
Base::Console().log("Loading GUI of Sandbox module... done\n");
|
||||
Base::Console().log("Loading GUI of Sandbox module… done\n");
|
||||
PyMOD_Return(mod);
|
||||
}
|
||||
|
||||
@@ -90,10 +90,10 @@ CmdSandboxDocumentThread::CmdSandboxDocumentThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Run several threads");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Run Several Threads");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_Thread";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool1";
|
||||
}
|
||||
|
||||
@@ -117,10 +117,10 @@ CmdSandboxDocumentTestThread::CmdSandboxDocumentTestThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Test thread");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Test Thread");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_TestThread";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool1";
|
||||
}
|
||||
|
||||
@@ -141,10 +141,10 @@ CmdSandboxDocumentSaveThread::CmdSandboxDocumentSaveThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Save thread");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox save function");
|
||||
sMenuText = QT_TR_NOOP("Save Thread");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox save function");
|
||||
sWhatsThis = "Sandbox_SaveThread";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox save function");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdSandboxDocumentSaveThread::activated(int)
|
||||
@@ -169,10 +169,10 @@ CmdSandboxDocThreadWithSeq::CmdSandboxDocThreadWithSeq()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Thread and sequencer");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Thread and Sequencer");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_SeqThread";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool2";
|
||||
}
|
||||
|
||||
@@ -188,7 +188,7 @@ void CmdSandboxDocThreadWithSeq::activated(int)
|
||||
#else
|
||||
int max = 100000000;
|
||||
#endif
|
||||
Base::SequencerLauncher seq("Do something meaningful...", max);
|
||||
Base::SequencerLauncher seq("Do something meaningful…", max);
|
||||
double val=0;
|
||||
for (int i=0; i<max; i++) {
|
||||
for (int j=0; j<max; j++) {
|
||||
@@ -208,10 +208,10 @@ CmdSandboxDocThreadBusy::CmdSandboxDocThreadBusy()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Thread and no sequencer");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Thread and No Sequencer");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_BlockThread";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool3";
|
||||
}
|
||||
|
||||
@@ -245,10 +245,10 @@ CmdSandboxDocumentNoThread::CmdSandboxDocumentNoThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("GUI thread");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("GUI Thread");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_NoThread";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool4";
|
||||
}
|
||||
|
||||
@@ -276,10 +276,10 @@ CmdSandboxWorkerThread::CmdSandboxWorkerThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Worker thread");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Worker Thread");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_WorkerThread";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool1";
|
||||
}
|
||||
|
||||
@@ -299,10 +299,10 @@ CmdSandboxPythonLockThread::CmdSandboxPythonLockThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Locked Python threads");
|
||||
sToolTipText = QT_TR_NOOP("Use Python's thread module where each thread is locked");
|
||||
sMenuText = QT_TR_NOOP("Locked Python Threads");
|
||||
sToolTipText = QT_TR_NOOP("Uses Python's thread module where each thread is locked");
|
||||
sWhatsThis = "Sandbox_PythonLockThread";
|
||||
sStatusTip = QT_TR_NOOP("Use Python's thread module where each thread is locked");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdSandboxPythonLockThread::activated(int)
|
||||
@@ -334,10 +334,10 @@ CmdSandboxPythonNolockThread::CmdSandboxPythonNolockThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Unlocked Python threads");
|
||||
sToolTipText = QT_TR_NOOP("Use Python's thread module where each thread is unlocked");
|
||||
sMenuText = QT_TR_NOOP("Unlocked Python Threads");
|
||||
sToolTipText = QT_TR_NOOP("Uses Python's thread module where each thread is unlocked");
|
||||
sWhatsThis = "Sandbox_NolockPython";
|
||||
sStatusTip = QT_TR_NOOP("Use Python's thread module where each thread is unlocked");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdSandboxPythonNolockThread::activated(int)
|
||||
@@ -366,10 +366,10 @@ CmdSandboxPySideThread::CmdSandboxPySideThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("PySide threads");
|
||||
sToolTipText = QT_TR_NOOP("Use PySide's thread module");
|
||||
sMenuText = QT_TR_NOOP("PySide Threads");
|
||||
sToolTipText = QT_TR_NOOP("Uses PySide's thread module");
|
||||
sWhatsThis = "Sandbox_PySideThread";
|
||||
sStatusTip = QT_TR_NOOP("Use PySide's thread module");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdSandboxPySideThread::activated(int)
|
||||
@@ -401,10 +401,10 @@ CmdSandboxPythonThread::CmdSandboxPythonThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Python threads");
|
||||
sToolTipText = QT_TR_NOOP("Use class PythonThread running Python code in its run() method");
|
||||
sMenuText = QT_TR_NOOP("Python Threads");
|
||||
sToolTipText = QT_TR_NOOP("Uses class PythonThread running Python code in its run() method");
|
||||
sWhatsThis = "Sandbox_PythonThread";
|
||||
sStatusTip = QT_TR_NOOP("Use class PythonThread running Python code in its run() method");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdSandboxPythonThread::activated(int)
|
||||
@@ -427,10 +427,10 @@ CmdSandboxPythonMainThread::CmdSandboxPythonMainThread()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Python main thread");
|
||||
sToolTipText = QT_TR_NOOP("Run python code in main thread");
|
||||
sMenuText = QT_TR_NOOP("Python Main Thread");
|
||||
sToolTipText = QT_TR_NOOP("Runs Python code in the main thread");
|
||||
sWhatsThis = "Sandbox_PythonMainThread";
|
||||
sStatusTip = QT_TR_NOOP("Run python code in main thread");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdSandboxPythonMainThread::activated(int)
|
||||
@@ -453,10 +453,10 @@ CmdSandboxDocThreadWithDialog::CmdSandboxDocThreadWithDialog()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Thread and modal dialog");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Thread and Modal Dialog");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_Dialog";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool7";
|
||||
}
|
||||
|
||||
@@ -480,10 +480,10 @@ CmdSandboxDocThreadWithFileDlg::CmdSandboxDocThreadWithFileDlg()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Thread and file dialog");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Thread and File Dialog");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_FileDialog";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool7";
|
||||
}
|
||||
|
||||
@@ -518,10 +518,10 @@ CmdSandboxEventLoop::CmdSandboxEventLoop()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Local event loop");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Local Event Loop");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_EventLoop";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool6";
|
||||
}
|
||||
|
||||
@@ -561,10 +561,10 @@ CmdSandboxMeshLoader::CmdSandboxMeshLoader()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Load mesh in thread");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Load Mesh in Thread");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_MeshLoad";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool6";
|
||||
}
|
||||
|
||||
@@ -619,10 +619,10 @@ CmdSandboxMeshLoaderBoost::CmdSandboxMeshLoaderBoost()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Load mesh in boost-thread");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Load Mesh in Boost-Thread");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_MeshLoaderBoost";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool6";
|
||||
}
|
||||
|
||||
@@ -667,10 +667,10 @@ CmdSandboxMeshLoaderFuture::CmdSandboxMeshLoaderFuture()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Load mesh in QFuture");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Load Mesh in QFuture");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_MeshLoaderFuture";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool6";
|
||||
}
|
||||
|
||||
@@ -782,10 +782,10 @@ CmdSandboxMeshTestJob::CmdSandboxMeshTestJob()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Test mesh job");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Test Mesh Job");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_MeshTestJob";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
sPixmap = "Std_Tool7";
|
||||
}
|
||||
|
||||
@@ -822,7 +822,7 @@ void CmdSandboxMeshTestJob::activated(int)
|
||||
}
|
||||
|
||||
// run the actual multi-threaded mesh test
|
||||
Base::Console().message("Mesh test (step %d)...\n",iteration++);
|
||||
Base::Console().message("Mesh test (step %d)…\n",iteration++);
|
||||
MeshTestJob meshJob;
|
||||
QFuture<Mesh::MeshObject*> mesh_future = QtConcurrent::mapped
|
||||
(mesh_groups, boost::bind(&MeshTestJob::run, &meshJob, bp::_1));
|
||||
@@ -877,10 +877,10 @@ CmdSandboxMeshTestRef::CmdSandboxMeshTestRef()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Test mesh reference");
|
||||
sToolTipText = QT_TR_NOOP("Sandbox Test function");
|
||||
sMenuText = QT_TR_NOOP("Test Mesh Reference");
|
||||
sToolTipText = QT_TR_NOOP("Runs a sandbox test function");
|
||||
sWhatsThis = "Sandbox_MeshTestRef";
|
||||
sStatusTip = QT_TR_NOOP("Sandbox Test function");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdSandboxMeshTestRef::activated(int)
|
||||
@@ -924,8 +924,8 @@ CmdTestGrabWidget::CmdTestGrabWidget()
|
||||
: Command("Std_GrabWidget")
|
||||
{
|
||||
sGroup = "Standard-Test";
|
||||
sMenuText = "Grab widget";
|
||||
sToolTipText = "Grab widget";
|
||||
sMenuText = "Grab Widget";
|
||||
sToolTipText = "Grabs a widget";
|
||||
sWhatsThis = "Std_GrabWidget";
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
@@ -1045,7 +1045,7 @@ CmdTestImageNode::CmdTestImageNode()
|
||||
: Command("Std_ImageNode")
|
||||
{
|
||||
sGroup = "Standard-Test";
|
||||
sMenuText = "SoImage node";
|
||||
sMenuText = "SoImage Node";
|
||||
sToolTipText = "SoImage node";
|
||||
sWhatsThis = "Std_ImageNode";
|
||||
sStatusTip = sToolTipText;
|
||||
@@ -1109,7 +1109,7 @@ CmdTestGDIWidget::CmdTestGDIWidget()
|
||||
: Command("Sandbox_GDIWidget")
|
||||
{
|
||||
sGroup = "Standard-Test";
|
||||
sMenuText = "GDI widget";
|
||||
sMenuText = "GDI Widget";
|
||||
sToolTipText = "GDI widget";
|
||||
sWhatsThis = "Sandbox_GDIWidget";
|
||||
sStatusTip = sToolTipText;
|
||||
@@ -1135,7 +1135,7 @@ CmdTestRedirectPaint::CmdTestRedirectPaint()
|
||||
: Command("Sandbox_RedirectPaint")
|
||||
{
|
||||
sGroup = "Standard-Test";
|
||||
sMenuText = "Redirect paint";
|
||||
sMenuText = "Redirect Paint";
|
||||
sToolTipText = "Redirect paint";
|
||||
sWhatsThis = "Sandbox_RedirectPaint";
|
||||
sStatusTip = sToolTipText;
|
||||
@@ -1165,7 +1165,7 @@ CmdTestCryptographicHash::CmdTestCryptographicHash()
|
||||
{
|
||||
sGroup = "Standard-Test";
|
||||
sMenuText = "Cryptographic Hash";
|
||||
sToolTipText = "Cryptographic Hash";
|
||||
sToolTipText = "Cryptographic hash";
|
||||
sWhatsThis = "Sandbox_CryptographicHash";
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
@@ -1186,7 +1186,7 @@ CmdTestWidgetShape::CmdTestWidgetShape()
|
||||
: Command("Sandbox_WidgetShape")
|
||||
{
|
||||
sGroup = "Standard-Test";
|
||||
sMenuText = "Widget shape";
|
||||
sMenuText = "Widget Shape";
|
||||
sToolTipText = "Widget shape";
|
||||
sWhatsThis = "Sandbox_WidgetShape";
|
||||
sStatusTip = sToolTipText;
|
||||
@@ -1210,10 +1210,10 @@ CmdMengerSponge::CmdMengerSponge()
|
||||
{
|
||||
sAppModule = "Sandbox";
|
||||
sGroup = QT_TR_NOOP("Sandbox");
|
||||
sMenuText = QT_TR_NOOP("Menger sponge");
|
||||
sMenuText = QT_TR_NOOP("Menger Sponge");
|
||||
sToolTipText = QT_TR_NOOP("Menger sponge");
|
||||
sWhatsThis = "Sandbox_MengerSponge";
|
||||
sStatusTip = QT_TR_NOOP("Menger sponge");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
struct Param {
|
||||
@@ -1341,7 +1341,7 @@ void CmdMengerSponge::activated(int)
|
||||
return;
|
||||
int ret = QMessageBox::question(Gui::getMainWindow(),
|
||||
QStringLiteral("Parallel"),
|
||||
QStringLiteral("Do you want to run this in a thread pool?"),
|
||||
QStringLiteral("Run this in a thread pool?"),
|
||||
QMessageBox::Yes|QMessageBox::No);
|
||||
bool parallel=(ret == QMessageBox::Yes);
|
||||
float x0=0,y0=0,z0=0;
|
||||
@@ -1379,9 +1379,9 @@ CmdTestGraphicsView::CmdTestGraphicsView()
|
||||
: Command("Std_TestGraphicsView")
|
||||
{
|
||||
sGroup = QT_TR_NOOP("Standard-Test");
|
||||
sMenuText = QT_TR_NOOP("Create new graphics view");
|
||||
sMenuText = QT_TR_NOOP("New Graphics View");
|
||||
sToolTipText= QT_TR_NOOP("Creates a new view window for the active document");
|
||||
sStatusTip = QT_TR_NOOP("Creates a new view window for the active document");
|
||||
sStatusTip = sToolTipText;
|
||||
}
|
||||
|
||||
void CmdTestGraphicsView::activated(int)
|
||||
@@ -1407,7 +1407,7 @@ CmdTestTaskBox::CmdTestTaskBox()
|
||||
: Command("Std_TestTaskBox")
|
||||
{
|
||||
sGroup = "Standard-Test";
|
||||
sMenuText = "Task box";
|
||||
sMenuText = "Task Box";
|
||||
sToolTipText = "Task box";
|
||||
sWhatsThis = "Std_TestTaskBox";
|
||||
sStatusTip = sToolTipText;
|
||||
|
||||
@@ -8632,9 +8632,6 @@ void SketchObject::rebuildExternalGeometry(std::optional<ExternalToAdd> extToAdd
|
||||
auto SubElements = ExternalGeometry.getSubValues();
|
||||
assert(externalGeoRef.size() == Objects.size());
|
||||
auto keys = externalGeoRef;
|
||||
if (Types.size() != Objects.size()) {
|
||||
Types.resize(Objects.size(), 0);
|
||||
}
|
||||
|
||||
// re-check for any missing geometry element. The code here has a side
|
||||
// effect that the linked external geometry will continue to work even if
|
||||
@@ -8695,6 +8692,10 @@ void SketchObject::rebuildExternalGeometry(std::optional<ExternalToAdd> extToAdd
|
||||
BRepBuilderAPI_MakeFace mkFace(sketchPlane);
|
||||
TopoDS_Shape aProjFace = mkFace.Shape();
|
||||
|
||||
if (Types.size() != Objects.size()) {
|
||||
Types.resize(Objects.size(), 0);
|
||||
}
|
||||
|
||||
std::set<std::string> refSet;
|
||||
// We use a vector here to keep the order (roughly) the same as ExternalGeometry
|
||||
std::vector<std::vector<std::unique_ptr<Part::Geometry> > > newGeos;
|
||||
|
||||
@@ -293,50 +293,50 @@ void CmdSketcherCompBSplineShowHideGeometryInformation::languageChange()
|
||||
|
||||
QAction* c1 = a[0];
|
||||
c1->setText(QApplication::translate("CmdSketcherCompBSplineShowHideGeometryInformation",
|
||||
"Show/hide B-spline degree"));
|
||||
c1->setToolTip(QApplication::translate(
|
||||
"Sketcher_BSplineDegree",
|
||||
"Switches between showing and hiding the degree for all B-splines"));
|
||||
c1->setStatusTip(QApplication::translate(
|
||||
"Sketcher_BSplineDegree",
|
||||
"Switches between showing and hiding the degree for all B-splines"));
|
||||
"Toggle B-Spline Degree"));
|
||||
c1->setToolTip(
|
||||
QApplication::translate("Sketcher_BSplineDegree",
|
||||
"Toggles the visibility of the degree for all B-splines"));
|
||||
c1->setStatusTip(
|
||||
QApplication::translate("Sketcher_BSplineDegree",
|
||||
"Toggles the visibility of the degree for all B-splines"));
|
||||
QAction* c2 = a[1];
|
||||
c2->setText(QApplication::translate("CmdSketcherCompBSplineShowHideGeometryInformation",
|
||||
"Show/hide B-spline control polygon"));
|
||||
"Toggle B-Spline Control Polygon"));
|
||||
c2->setToolTip(QApplication::translate(
|
||||
"Sketcher_BSplinePolygon",
|
||||
"Switches between showing and hiding the control polygons for all B-splines"));
|
||||
"Toggles the visibility of the control polygons for all B-splines"));
|
||||
c2->setStatusTip(QApplication::translate(
|
||||
"Sketcher_BSplinePolygon",
|
||||
"Switches between showing and hiding the control polygons for all B-splines"));
|
||||
"Toggles the visibility of the control polygons for all B-splines"));
|
||||
QAction* c3 = a[2];
|
||||
c3->setText(QApplication::translate("CmdSketcherCompBSplineShowHideGeometryInformation",
|
||||
"Show/hide B-spline curvature comb"));
|
||||
c3->setToolTip(QApplication::translate(
|
||||
"Sketcher_BSplineComb",
|
||||
"Switches between showing and hiding the curvature comb for all B-splines"));
|
||||
c3->setStatusTip(QApplication::translate(
|
||||
"Sketcher_BSplineComb",
|
||||
"Switches between showing and hiding the curvature comb for all B-splines"));
|
||||
"Toggle B-Spline Curvature Comb"));
|
||||
c3->setToolTip(
|
||||
QApplication::translate("Sketcher_BSplineComb",
|
||||
"Toggles the visibility of the curvature comb for all B-splines"));
|
||||
c3->setStatusTip(
|
||||
QApplication::translate("Sketcher_BSplineComb",
|
||||
"Toggles the visibility of the curvature comb for all B-splines"));
|
||||
QAction* c4 = a[3];
|
||||
c4->setText(QApplication::translate("CmdSketcherCompBSplineShowHideGeometryInformation",
|
||||
"Show/hide B-spline knot multiplicity"));
|
||||
"Toggle B-Spline Knot Multiplicity"));
|
||||
c4->setToolTip(QApplication::translate(
|
||||
"Sketcher_BSplineKnotMultiplicity",
|
||||
"Switches between showing and hiding the knot multiplicity for all B-splines"));
|
||||
"Toggles the visibility of the knot multiplicity for all B-splines"));
|
||||
c4->setStatusTip(QApplication::translate(
|
||||
"Sketcher_BSplineKnotMultiplicity",
|
||||
"Switches between showing and hiding the knot multiplicity for all B-splines"));
|
||||
"Toggles the visibility of the knot multiplicity for all B-splines"));
|
||||
|
||||
QAction* c5 = a[4];
|
||||
c5->setText(QApplication::translate("CmdSketcherCompBSplineShowHideGeometryInformation",
|
||||
"Show/hide B-spline control point weight"));
|
||||
"Toggle B-Spline Control Point Weight"));
|
||||
c5->setToolTip(QApplication::translate(
|
||||
"Sketcher_BSplinePoleWeight",
|
||||
"Switches between showing and hiding the control point weight for all B-splines"));
|
||||
"Toggles the visibility of the control point weight for all B-splines"));
|
||||
c5->setStatusTip(QApplication::translate(
|
||||
"Sketcher_BSplinePoleWeight",
|
||||
"Switches between showing and hiding the control point weight for all B-splines"));
|
||||
"Toggles the visibility of the control point weight for all B-splines"));
|
||||
}
|
||||
|
||||
void CmdSketcherCompBSplineShowHideGeometryInformation::updateAction(int /*mode*/)
|
||||
|
||||
@@ -2406,7 +2406,7 @@ CmdSketcherRotate::CmdSketcherRotate()
|
||||
{
|
||||
sAppModule = "Sketcher";
|
||||
sGroup = "Sketcher";
|
||||
sMenuText = QT_TR_NOOP("Rotate/Polar Transform");
|
||||
sMenuText = QT_TR_NOOP("Rotate / Polar Transform");
|
||||
sToolTipText = QT_TR_NOOP("Rotates the selected geometry by creating 'n' copies, enabling circular pattern creation");
|
||||
sWhatsThis = "Sketcher_Rotate";
|
||||
sStatusTip = sToolTipText;
|
||||
|
||||
@@ -412,12 +412,22 @@ void EditDatumDialog::performAutoScale(double newDatum)
|
||||
&& sketch->getExternalGeometryCount() <= 2 && sketch->hasSingleScaleDefiningConstraint()) {
|
||||
try {
|
||||
double oldDatum = sketch->getDatum(ConstrNbr);
|
||||
double scale_factor = newDatum / oldDatum;
|
||||
double scaleFactor = newDatum / oldDatum;
|
||||
float initLabelDistance = sketch->Constraints[ConstrNbr]->LabelDistance;
|
||||
float initLabelPosition = sketch->Constraints[ConstrNbr]->LabelPosition;
|
||||
centerScale(sketch, scale_factor);
|
||||
sketch->setLabelDistance(ConstrNbr, initLabelDistance * scale_factor);
|
||||
sketch->setLabelPosition(ConstrNbr, initLabelPosition * scale_factor);
|
||||
centerScale(sketch, scaleFactor);
|
||||
sketch->setLabelDistance(ConstrNbr, initLabelDistance * scaleFactor);
|
||||
|
||||
// Label position or radii and diameters represent an angle, so
|
||||
// they should not be scaled
|
||||
Sketcher::ConstraintType type = sketch->Constraints[ConstrNbr]->Type;
|
||||
if (type == Sketcher::ConstraintType::Radius
|
||||
|| type == Sketcher::ConstraintType::Diameter) {
|
||||
sketch->setLabelPosition(ConstrNbr, initLabelPosition);
|
||||
}
|
||||
else {
|
||||
sketch->setLabelPosition(ConstrNbr, initLabelPosition * scaleFactor);
|
||||
}
|
||||
}
|
||||
catch (const Base::Exception& e) {
|
||||
Base::Console().error("Exception performing autoscale: %s\n", e.what());
|
||||
|
||||
@@ -293,21 +293,12 @@ This setting is only for the toolbar. Whichever you choose, all tools are always
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="autoScaleMode">
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Always</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Never</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>Only if there is no visual scale indicator</string>
|
||||
</property>
|
||||
</item>
|
||||
<property name="toolTip">
|
||||
<string>Select the mode of automatic geometry scaling upon first dimension:
|
||||
'Always': Automatic scaling upon first dimension is always performed.
|
||||
'Never': Automatic scaling upon first dimension is never performed.
|
||||
'When no scale feature is visible': Automatic scaling upon first dimension is only performed if there are no visible objects in the 3D view.</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
|
||||
@@ -37,7 +37,7 @@ using namespace SketcherGui;
|
||||
qApp->translate("Workbench","P&rofiles");
|
||||
qApp->translate("Workbench","S&ketch");
|
||||
qApp->translate("Workbench", "Sketcher");
|
||||
qApp->translate("Workbench", "Sketcher Edit Mode");
|
||||
qApp->translate("Workbench", "Edit Mode");
|
||||
|
||||
qApp->translate("Workbench", "Geometries");
|
||||
qApp->translate("Workbench", "Constraints");
|
||||
@@ -113,7 +113,7 @@ Gui::ToolBarItem* Workbench::setupToolBars() const
|
||||
|
||||
Gui::ToolBarItem* sketcherEditMode =
|
||||
new Gui::ToolBarItem(root, Gui::ToolBarItem::DefaultVisibility::Unavailable);
|
||||
sketcherEditMode->setCommand("Sketcher Edit Mode");
|
||||
sketcherEditMode->setCommand("Edit Mode");
|
||||
addSketcherWorkbenchSketchEditModeActions(*sketcherEditMode);
|
||||
|
||||
Gui::ToolBarItem* geom =
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
target_sources(Tests_run PRIVATE
|
||||
Assistant.cpp
|
||||
Camera.cpp
|
||||
StyleParameters/StyleParametersApplicationTest.cpp
|
||||
StyleParameters/ParserTest.cpp
|
||||
StyleParameters/ParameterManagerTest.cpp
|
||||
)
|
||||
|
||||
# Qt tests
|
||||
|
||||
@@ -0,0 +1,326 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <kacper@kadet.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/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/StyleParameters/ParameterManager.h>
|
||||
|
||||
using namespace Gui::StyleParameters;
|
||||
|
||||
class ParameterManagerTest: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
// Create test sources
|
||||
auto source1 = std::make_unique<InMemoryParameterSource>(
|
||||
std::list<Parameter> {
|
||||
{"BaseSize", "8px"},
|
||||
{"PrimaryColor", "#ff0000"},
|
||||
{"SecondaryColor", "#00ff00"},
|
||||
},
|
||||
ParameterSource::Metadata {"Source 1"});
|
||||
|
||||
auto source2 = std::make_unique<InMemoryParameterSource>(
|
||||
std::list<Parameter> {
|
||||
{"BaseSize", "16px"}, // Override from source1
|
||||
{"Margin", "@BaseSize * 2"},
|
||||
{"Padding", "@BaseSize / 2"},
|
||||
},
|
||||
ParameterSource::Metadata {"Source 2"});
|
||||
|
||||
manager.addSource(source1.get());
|
||||
manager.addSource(source2.get());
|
||||
sources.push_back(std::move(source1));
|
||||
sources.push_back(std::move(source2));
|
||||
}
|
||||
|
||||
Gui::StyleParameters::ParameterManager manager;
|
||||
std::vector<std::unique_ptr<ParameterSource>> sources;
|
||||
};
|
||||
|
||||
// Test basic parameter resolution
|
||||
TEST_F(ParameterManagerTest, BasicParameterResolution)
|
||||
{
|
||||
{
|
||||
auto result = manager.resolve("BaseSize");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 16.0); // Should get value from source2 (later source)
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
auto result = manager.resolve("PrimaryColor");
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 255);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
auto result = manager.resolve("SecondaryColor");
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 0);
|
||||
EXPECT_EQ(color.green(), 255);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test parameter references
|
||||
TEST_F(ParameterManagerTest, ParameterReferences)
|
||||
{
|
||||
{
|
||||
auto result = manager.resolve("Margin");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 32.0); // @BaseSize * 2 = 16 * 2 = 32
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
auto result = manager.resolve("Padding");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 8.0); // @BaseSize / 2 = 16 / 2 = 8
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
}
|
||||
|
||||
// Test caching
|
||||
TEST_F(ParameterManagerTest, Caching)
|
||||
{
|
||||
// First resolution should cache the result
|
||||
auto result1 = manager.resolve("BaseSize");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result1));
|
||||
|
||||
// Second resolution should use cached value
|
||||
auto result2 = manager.resolve("BaseSize");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result2));
|
||||
|
||||
// Results should be identical
|
||||
auto length1 = std::get<Length>(result1);
|
||||
auto length2 = std::get<Length>(result2);
|
||||
EXPECT_DOUBLE_EQ(length1.value, length2.value);
|
||||
EXPECT_EQ(length1.unit, length2.unit);
|
||||
}
|
||||
|
||||
// Test cache invalidation
|
||||
TEST_F(ParameterManagerTest, CacheInvalidation)
|
||||
{
|
||||
// Initial resolution
|
||||
auto result1 = manager.resolve("BaseSize");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result1));
|
||||
auto length1 = std::get<Length>(result1);
|
||||
EXPECT_DOUBLE_EQ(length1.value, 16.0);
|
||||
|
||||
// Reload should clear cache
|
||||
manager.reload();
|
||||
|
||||
// Resolution after reload should work the same
|
||||
auto result2 = manager.resolve("BaseSize");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result2));
|
||||
auto length2 = std::get<Length>(result2);
|
||||
EXPECT_DOUBLE_EQ(length2.value, 16.0);
|
||||
EXPECT_EQ(length1.unit, length2.unit);
|
||||
}
|
||||
|
||||
// Test source priority
|
||||
TEST_F(ParameterManagerTest, SourcePriority)
|
||||
{
|
||||
// Create a third source with higher priority
|
||||
auto source3 = std::make_unique<InMemoryParameterSource>(
|
||||
std::list<Parameter> {
|
||||
{"BaseSize", "24px"}, // Should override both previous sources
|
||||
},
|
||||
ParameterSource::Metadata {"Source 3"});
|
||||
|
||||
manager.addSource(source3.get());
|
||||
sources.push_back(std::move(source3));
|
||||
|
||||
// Should get value from the latest source (highest priority)
|
||||
auto result = manager.resolve("BaseSize");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 24.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
// Test parameter listing
|
||||
TEST_F(ParameterManagerTest, ParameterListing)
|
||||
{
|
||||
auto params = manager.parameters();
|
||||
|
||||
// Should contain all parameters from all sources
|
||||
std::set<std::string> paramNames;
|
||||
for (const auto& param : params) {
|
||||
paramNames.insert(param.name);
|
||||
}
|
||||
|
||||
EXPECT_TRUE(paramNames.contains("BaseSize"));
|
||||
EXPECT_TRUE(paramNames.contains("PrimaryColor"));
|
||||
EXPECT_TRUE(paramNames.contains("SecondaryColor"));
|
||||
EXPECT_TRUE(paramNames.contains("Margin"));
|
||||
EXPECT_TRUE(paramNames.contains("Padding"));
|
||||
|
||||
// Should not contain duplicates (BaseSize should appear only once)
|
||||
EXPECT_EQ(paramNames.count("BaseSize"), 1);
|
||||
}
|
||||
|
||||
// Test expression retrieval
|
||||
TEST_F(ParameterManagerTest, ExpressionRetrieval)
|
||||
{
|
||||
{
|
||||
auto expr = manager.expression("BaseSize");
|
||||
EXPECT_TRUE(expr.has_value());
|
||||
EXPECT_EQ(*expr, "16px");
|
||||
}
|
||||
|
||||
{
|
||||
auto expr = manager.expression("Margin");
|
||||
EXPECT_TRUE(expr.has_value());
|
||||
EXPECT_EQ(*expr, "@BaseSize * 2");
|
||||
}
|
||||
|
||||
{
|
||||
auto expr = manager.expression("NonExistent");
|
||||
EXPECT_FALSE(expr.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Test parameter retrieval
|
||||
TEST_F(ParameterManagerTest, ParameterRetrieval)
|
||||
{
|
||||
{
|
||||
auto param = manager.parameter("BaseSize");
|
||||
EXPECT_TRUE(param.has_value());
|
||||
EXPECT_EQ(param->name, "BaseSize");
|
||||
EXPECT_EQ(param->value, "16px");
|
||||
}
|
||||
|
||||
{
|
||||
auto param = manager.parameter("NonExistent");
|
||||
EXPECT_FALSE(param.has_value());
|
||||
}
|
||||
}
|
||||
|
||||
// Test source management
|
||||
TEST_F(ParameterManagerTest, SourceManagement)
|
||||
{
|
||||
auto sources = manager.sources();
|
||||
EXPECT_EQ(sources.size(), 2); // We added 2 sources in SetUp
|
||||
|
||||
// Test that we can access the sources
|
||||
for (auto source : sources) {
|
||||
EXPECT_NE(source, nullptr);
|
||||
auto params = source->all();
|
||||
EXPECT_FALSE(params.empty());
|
||||
}
|
||||
}
|
||||
|
||||
// Test circular reference detection
|
||||
TEST_F(ParameterManagerTest, CircularReferenceDetection)
|
||||
{
|
||||
// Create a source with circular reference
|
||||
auto circularSource = std::make_unique<InMemoryParameterSource>(
|
||||
std::list<Parameter> {
|
||||
{"A", "@B"},
|
||||
{"B", "@A"},
|
||||
},
|
||||
ParameterSource::Metadata {"Circular Source"});
|
||||
|
||||
manager.addSource(circularSource.get());
|
||||
sources.push_back(std::move(circularSource));
|
||||
|
||||
// Should handle circular reference gracefully
|
||||
auto result = manager.resolve("A");
|
||||
// Should return the expression string as fallback
|
||||
EXPECT_TRUE(std::holds_alternative<std::string>(result));
|
||||
}
|
||||
|
||||
// Test complex expressions
|
||||
TEST_F(ParameterManagerTest, ComplexExpressions)
|
||||
{
|
||||
// Create a source with complex expressions
|
||||
auto complexSource = std::make_unique<InMemoryParameterSource>(
|
||||
std::list<Parameter> {
|
||||
{"ComplexMargin", "(@BaseSize + 4px) * 2"},
|
||||
{"ComplexPadding", "(@BaseSize - 2px) / 2"},
|
||||
{"ColorWithFunction", "lighten(@PrimaryColor, 20)"},
|
||||
},
|
||||
ParameterSource::Metadata {"Complex Source"});
|
||||
|
||||
manager.addSource(complexSource.get());
|
||||
sources.push_back(std::move(complexSource));
|
||||
|
||||
{
|
||||
auto result = manager.resolve("ComplexMargin");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 40.0); // (16 + 4) * 2 = 20 * 2 = 40
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
auto result = manager.resolve("ComplexPadding");
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 7.0); // (16 - 2) / 2 = 14 / 2 = 7
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
auto result = manager.resolve("ColorWithFunction");
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
// Should be lighter than the original red
|
||||
EXPECT_GT(color.lightness(), QColor("#ff0000").lightness());
|
||||
}
|
||||
}
|
||||
|
||||
// Test error handling
|
||||
TEST_F(ParameterManagerTest, ErrorHandling)
|
||||
{
|
||||
// Test non-existent parameter
|
||||
auto result = manager.resolve("NonExistent");
|
||||
EXPECT_TRUE(std::holds_alternative<std::string>(result));
|
||||
EXPECT_EQ(std::get<std::string>(result), "");
|
||||
|
||||
// Test invalid expression
|
||||
auto invalidSource = std::make_unique<InMemoryParameterSource>(
|
||||
std::list<Parameter> {
|
||||
{"Invalid", "invalid expression that will fail"},
|
||||
},
|
||||
ParameterSource::Metadata {"Invalid Source"});
|
||||
|
||||
manager.addSource(invalidSource.get());
|
||||
sources.push_back(std::move(invalidSource));
|
||||
|
||||
// Should handle invalid expression gracefully
|
||||
auto invalidResult = manager.resolve("Invalid");
|
||||
// Should return the expression string as fallback
|
||||
EXPECT_TRUE(std::holds_alternative<std::string>(invalidResult));
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <kacper@kadet.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/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <QColor>
|
||||
|
||||
#include <Gui/StyleParameters/Parser.h>
|
||||
#include <Gui/StyleParameters/ParameterManager.h>
|
||||
|
||||
using namespace Gui::StyleParameters;
|
||||
|
||||
class ParserTest: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
void SetUp() override
|
||||
{
|
||||
// Create a simple parameter manager for testing
|
||||
auto source = std::make_unique<InMemoryParameterSource>(
|
||||
std::list<Parameter> {
|
||||
{"TestParam", "10px"},
|
||||
{"TestColor", "#ff0000"},
|
||||
{"TestNumber", "5"},
|
||||
},
|
||||
ParameterSource::Metadata {"Test Source"});
|
||||
|
||||
manager.addSource(source.get());
|
||||
sources.push_back(std::move(source));
|
||||
}
|
||||
|
||||
Gui::StyleParameters::ParameterManager manager;
|
||||
std::vector<std::unique_ptr<ParameterSource>> sources;
|
||||
};
|
||||
|
||||
// Test number parsing
|
||||
TEST_F(ParserTest, ParseNumbers)
|
||||
{
|
||||
{
|
||||
Parser parser("42");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 42.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10.5px");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 10.5);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("2.5em");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 2.5);
|
||||
EXPECT_EQ(length.unit, "em");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("100%");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 100.0);
|
||||
EXPECT_EQ(length.unit, "%");
|
||||
}
|
||||
}
|
||||
|
||||
// Test color parsing
|
||||
TEST_F(ParserTest, ParseColors)
|
||||
{
|
||||
{
|
||||
Parser parser("#ff0000");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 255);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("#00ff00");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 0);
|
||||
EXPECT_EQ(color.green(), 255);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("#0000ff");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 0);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 255);
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("rgb(255, 0, 0)");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 255);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("rgba(255, 0, 0, 128)");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 255);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
EXPECT_EQ(color.alpha(), 128);
|
||||
}
|
||||
}
|
||||
|
||||
// Test parameter reference parsing
|
||||
TEST_F(ParserTest, ParseParameterReferences)
|
||||
{
|
||||
{
|
||||
Parser parser("@TestParam");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 10.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("@TestColor");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({.manager = &manager, .context = {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 255);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("@TestNumber");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({.manager = &manager, .context = {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 5.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
}
|
||||
|
||||
// Test arithmetic operations
|
||||
TEST_F(ParserTest, ParseArithmeticOperations)
|
||||
{
|
||||
{
|
||||
Parser parser("10 + 5");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 15.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10px + 5px");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 15.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10 - 5");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 5.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10px - 5px");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 5.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10 * 5");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 50.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10px * 2");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 20.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10 / 2");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 5.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10px / 2");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 5.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
}
|
||||
|
||||
// Test complex expressions
|
||||
TEST_F(ParserTest, ParseComplexExpressions)
|
||||
{
|
||||
{
|
||||
Parser parser("(10 + 5) * 2");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 30.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("(10px + 5px) * 2");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 30.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("@TestParam + 5px");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 15.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("@TestParam * @TestNumber");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 50.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
}
|
||||
|
||||
// Test unary operations
|
||||
TEST_F(ParserTest, ParseUnaryOperations)
|
||||
{
|
||||
{
|
||||
Parser parser("+10");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 10.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("-10");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, -10.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("-10px");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, -10.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
}
|
||||
|
||||
// Test function calls
|
||||
TEST_F(ParserTest, ParseFunctionCalls)
|
||||
{
|
||||
{
|
||||
Parser parser("lighten(#ff0000, 20)");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
// The result should be lighter than the original red
|
||||
EXPECT_GT(color.lightness(), QColor("#ff0000").lightness());
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("darken(#ff0000, 20)");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
// The result should be darker than the original red
|
||||
EXPECT_LT(color.lightness(), QColor("#ff0000").lightness());
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("lighten(@TestColor, 20)");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
// The result should be lighter than the original red
|
||||
EXPECT_GT(color.lightness(), QColor("#ff0000").lightness());
|
||||
}
|
||||
}
|
||||
|
||||
// Test error cases
|
||||
TEST_F(ParserTest, ParseErrors)
|
||||
{
|
||||
// Invalid color format
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("#invalid");
|
||||
parser.parse();
|
||||
},
|
||||
Base::ParserError);
|
||||
|
||||
// Invalid RGB format
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("rgb(invalid)");
|
||||
parser.parse();
|
||||
},
|
||||
Base::ParserError);
|
||||
|
||||
// Missing closing parenthesis
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("(10 + 5");
|
||||
parser.parse();
|
||||
},
|
||||
Base::ParserError);
|
||||
|
||||
// Invalid function
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("invalid()");
|
||||
auto expr = parser.parse();
|
||||
expr->evaluate({&manager, {}});
|
||||
},
|
||||
Base::ExpressionError);
|
||||
|
||||
// Division by zero
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("10 / 0");
|
||||
auto expr = parser.parse();
|
||||
expr->evaluate({&manager, {}});
|
||||
},
|
||||
Base::RuntimeError);
|
||||
|
||||
// Unit mismatch
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("10px + 5em");
|
||||
auto expr = parser.parse();
|
||||
expr->evaluate({&manager, {}});
|
||||
},
|
||||
Base::RuntimeError);
|
||||
|
||||
// Unary operation on color
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("-@TestColor");
|
||||
auto expr = parser.parse();
|
||||
expr->evaluate({&manager, {}});
|
||||
},
|
||||
Base::ExpressionError);
|
||||
|
||||
// Function with wrong number of arguments
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("lighten(#ff0000)");
|
||||
auto expr = parser.parse();
|
||||
expr->evaluate({&manager, {}});
|
||||
},
|
||||
Base::ExpressionError);
|
||||
|
||||
// Function with wrong argument type
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("lighten(10px, 20)");
|
||||
auto expr = parser.parse();
|
||||
expr->evaluate({&manager, {}});
|
||||
},
|
||||
Base::ExpressionError);
|
||||
}
|
||||
|
||||
// Test whitespace handling
|
||||
TEST_F(ParserTest, ParseWhitespace)
|
||||
{
|
||||
{
|
||||
Parser parser(" 10 + 5 ");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 15.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10px+5px");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 15.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("rgb(255,0,0)");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 255);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Test edge cases
|
||||
TEST_F(ParserTest, ParseEdgeCases)
|
||||
{
|
||||
// Empty input
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser("");
|
||||
parser.parse();
|
||||
},
|
||||
Base::ParserError);
|
||||
|
||||
// Just whitespace
|
||||
EXPECT_THROW(
|
||||
{
|
||||
Parser parser(" ");
|
||||
parser.parse();
|
||||
},
|
||||
Base::ParserError);
|
||||
|
||||
// Single number
|
||||
{
|
||||
Parser parser("42");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 42.0);
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
// Single color
|
||||
{
|
||||
Parser parser("#ff0000");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<QColor>(result));
|
||||
auto color = std::get<QColor>(result);
|
||||
EXPECT_EQ(color.red(), 255);
|
||||
EXPECT_EQ(color.green(), 0);
|
||||
EXPECT_EQ(color.blue(), 0);
|
||||
}
|
||||
|
||||
// Single parameter reference
|
||||
{
|
||||
Parser parser("@TestParam");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 10.0);
|
||||
EXPECT_EQ(length.unit, "px");
|
||||
}
|
||||
}
|
||||
|
||||
// Test operator precedence
|
||||
TEST_F(ParserTest, ParseOperatorPrecedence)
|
||||
{
|
||||
{
|
||||
Parser parser("2 + 3 * 4");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 14.0); // 2 + (3 * 4) = 2 + 12 = 14
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("10 - 3 * 2");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 4.0); // 10 - (3 * 2) = 10 - 6 = 4
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("20 / 4 + 3");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 8.0); // (20 / 4) + 3 = 5 + 3 = 8
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
}
|
||||
|
||||
// Test nested parentheses
|
||||
TEST_F(ParserTest, ParseNestedParentheses)
|
||||
{
|
||||
{
|
||||
Parser parser("((2 + 3) * 4)");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 20.0); // (5) * 4 = 20
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
|
||||
{
|
||||
Parser parser("(10 - (3 + 2)) * 2");
|
||||
auto expr = parser.parse();
|
||||
auto result = expr->evaluate({&manager, {}});
|
||||
EXPECT_TRUE(std::holds_alternative<Length>(result));
|
||||
auto length = std::get<Length>(result);
|
||||
EXPECT_DOUBLE_EQ(length.value, 10.0); // (10 - 5) * 2 = 5 * 2 = 10
|
||||
EXPECT_EQ(length.unit, "");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2025 Kacper Donat <kacper@kadet.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/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "src/App/InitApplication.h"
|
||||
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/StyleParameters/ParameterManager.h>
|
||||
|
||||
using namespace Gui;
|
||||
|
||||
class StyleParametersApplicationTest: public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
static Application* app;
|
||||
|
||||
static void SetUpTestSuite()
|
||||
{
|
||||
tests::initApplication();
|
||||
app = new Application(true);
|
||||
}
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
auto styleParamManager = app->styleParameterManager();
|
||||
|
||||
styleParamManager->addSource(new StyleParameters::InMemoryParameterSource(
|
||||
{
|
||||
{.name = "ColorPrimary", .value = "#ff0000"},
|
||||
{.name = "FontSize", .value = "12px"},
|
||||
{.name = "BoxWidth", .value = "100px"},
|
||||
},
|
||||
{.name = "Fixture Source"}));
|
||||
}
|
||||
};
|
||||
|
||||
Application* StyleParametersApplicationTest::app = {};
|
||||
|
||||
// Test for replacing variables in QSS string
|
||||
TEST_F(StyleParametersApplicationTest, ReplaceVariablesInQss)
|
||||
{
|
||||
QString qss = "QWidget { color: @ColorPrimary; font-size: @FontSize; width: @BoxWidth; }";
|
||||
QString result = app->replaceVariablesInQss(qss);
|
||||
|
||||
EXPECT_EQ(result.toStdString(), "QWidget { color: #ff0000; font-size: 12px; width: 100px; }");
|
||||
}
|
||||
|
||||
// Test if unknown variables remain unchanged
|
||||
TEST_F(StyleParametersApplicationTest, ReplaceVariablesInQssWithUnknownVariable)
|
||||
{
|
||||
QString qss = "QWidget { color: @UnknownColor; margin: 10px; }";
|
||||
QString result = app->replaceVariablesInQss(qss);
|
||||
|
||||
EXPECT_EQ(result.toStdString(), "QWidget { color: ; margin: 10px; }");
|
||||
}
|
||||
|
||||
// Test with an empty QSS string
|
||||
TEST_F(StyleParametersApplicationTest, ReplaceVariablesInQssWithEmptyString)
|
||||
{
|
||||
QString qss = "";
|
||||
QString result = app->replaceVariablesInQss(qss);
|
||||
|
||||
EXPECT_EQ(result.toStdString(), "");
|
||||
}
|
||||
|
||||
// Test replacing multiple occurrences of the same variable
|
||||
TEST_F(StyleParametersApplicationTest, ReplaceVariablesInQssWithMultipleOccurrences)
|
||||
{
|
||||
QString qss = "QWidget { color: @ColorPrimary; background: @ColorPrimary; }";
|
||||
QString result = app->replaceVariablesInQss(qss);
|
||||
|
||||
EXPECT_EQ(result.toStdString(), "QWidget { color: #ff0000; background: #ff0000; }");
|
||||
}
|
||||
@@ -106,6 +106,7 @@ TEST_F(PartFeaturesTest, testSweep)
|
||||
auto _sweep = _doc->addObject<Sweep>();
|
||||
_sweep->Sections.setValues({_plane1});
|
||||
_sweep->Spine.setValue(_edge1);
|
||||
_sweep->Solid.setValue((false));
|
||||
// Act
|
||||
_sweep->execute();
|
||||
TopoShape ts = _sweep->Shape.getShape();
|
||||
|
||||
Reference in New Issue
Block a user