diff --git a/.github/workflows/ci.yml b/.github/workflows/ci_linux.yml similarity index 50% rename from .github/workflows/ci.yml rename to .github/workflows/ci_linux.yml index 87db274f..9703dc99 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci_linux.yml @@ -1,38 +1,32 @@ -name: CI +name: Linux on: [push] jobs: - ci: + build-linux: strategy: - fail-fast: false matrix: - os: [ubuntu-latest, macos-latest] + qt_version: [5.15.2, 6.5.2] - runs-on: ${{matrix.os}} + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - with: - path: 'source' - fetch-depth: 0 - lfs: 'false' - - - name: Cache Qt - id: cache-qt - uses: actions/cache@v1 - with: - path: ../Qt - key: ${{ runner.os }}-QtCache - name: Install Qt - uses: jurplel/install-qt-action@v2 + uses: jurplel/install-qt-action@v3 with: - cached: ${{ steps.cache-qt.outputs.cache-hit }} + version: ${{matrix.qt_version}} + cache: true + cache-key-prefix: QtCache + + # Actually needed for Qt6 + - name: Install dependencies for "xcb" Qt plugin + run: | + sudo apt-get -y install libxcb-cursor0 - - name: Install OpenCascade[Ubuntu] - if: startsWith(matrix.os, 'ubuntu') + - name: Install OpenCascade run: | sudo apt-get -y install libocct-data-exchange-dev libocct-draw-dev GH_CASCADE_INC_DIR=`dpkg -L libocct-foundation-dev | grep -i "Standard_Version.hxx" | sed "s/\/Standard_Version.hxx//i"` @@ -40,37 +34,38 @@ jobs: echo "GH_CASCADE_INC_DIR=$GH_CASCADE_INC_DIR" >> $GITHUB_ENV echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_LIB_DIR" >> $GITHUB_ENV - - name: Install OpenCascade[macOS] - if: startsWith(matrix.os, 'macos') + - name: Install Assimp run: | - brew install opencascade - GH_CASCADE_BASE_DIR=`brew info opencascade | grep -E -i --only-matching --max-count=1 "^(/[a-z\.\-_0-9]+)+"` - echo "GH_CASCADE_INC_DIR=$GH_CASCADE_BASE_DIR/include/opencascade" >> $GITHUB_ENV - echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_BASE_DIR/lib" >> $GITHUB_ENV + sudo apt-get -y install libassimp-dev + GH_ASSIMP_INC_DIR=`dpkg -L libassimp-dev | grep -i "version.h" | sed "s/\/version.h//i"` + GH_ASSIMP_LIB_DIR=`dpkg -L libassimp-dev | grep -i "libassimp.so" | sed "s/\/libassimp.so//i"` + echo "GH_ASSIMP_INC_DIR=$GH_ASSIMP_INC_DIR" >> $GITHUB_ENV + echo "GH_ASSIMP_LIB_DIR=$GH_ASSIMP_LIB_DIR" >> $GITHUB_ENV - name: Get count of CPU cores uses: SimenB/github-actions-cpu-cores@v1 id: cpu-cores - - name: Create Build folder - run: mkdir ${{github.workspace}}/build - - - name: QMake - working-directory: ${{github.workspace}}/build + - name: Build run: | + mkdir ${{github.workspace}}/build + cd ${{github.workspace}}/build echo CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} echo CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} + echo ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} + echo ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} [ ! -d $CASCADE_INC_DIR ] && echo "ERROR: OpenCascade include dir doesn't exist" [ ! -d $CASCADE_LIB_DIR ] && echo "ERROR: OpenCascade lib dir doesn't exist" - qmake ../source CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} CONFIG+=withtests - - - name: Build - working-directory: ${{github.workspace}}/build - run: | + [ ! -d $ASSIMP_INC_DIR ] && echo "ERROR: assimp include dir doesn't exist" + [ ! -d $ASSIMP_LIB_DIR ] && echo "ERROR: assimp lib dir doesn't exist" + qmake .. CONFIG+=withtests \ + CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} \ + CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} \ + ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} \ + ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} make -j${{steps.cpu-cores.outputs.count}} - - name: Execute Unit Tests[Ubuntu] - if: startsWith(matrix.os, 'ubuntu') + - name: Execute Unit Tests working-directory: ${{github.workspace}}/build env: DISPLAY: :0 @@ -84,9 +79,3 @@ jobs: sleep 5s # Run tests ./mayo --runtests - - - name: Execute Unit Tests[macOS] - if: startsWith(matrix.os, 'macos') - working-directory: ${{github.workspace}}/build - run: | - ./mayo.app/Contents/MacOS/mayo --runtests diff --git a/.github/workflows/ci_macos.yml b/.github/workflows/ci_macos.yml new file mode 100644 index 00000000..8161795a --- /dev/null +++ b/.github/workflows/ci_macos.yml @@ -0,0 +1,59 @@ +name: macOS + +on: [push] + +jobs: + build-macos: + runs-on: macos-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + cache: true + cache-key-prefix: QtCache + + - name: Install OpenCascade + run: | + brew install opencascade + GH_CASCADE_BASE_DIR=`brew info opencascade | grep -E -i --only-matching --max-count=1 "^(/[a-z\.\-_0-9]+)+"` + echo "GH_CASCADE_INC_DIR=$GH_CASCADE_BASE_DIR/include/opencascade" >> $GITHUB_ENV + echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_BASE_DIR/lib" >> $GITHUB_ENV + + - name: Install Assimp + run: | + brew install assimp + GH_ASSIMP_BASE_DIR=`brew info assimp | grep -E -i --only-matching --max-count=1 "^(/[a-z\.\-_0-9]+)+"` + echo "GH_ASSIMP_INC_DIR=$GH_ASSIMP_BASE_DIR/include/assimp" >> $GITHUB_ENV + echo "GH_ASSIMP_LIB_DIR=$GH_ASSIMP_BASE_DIR/lib" >> $GITHUB_ENV + + - name: Get count of CPU cores + uses: SimenB/github-actions-cpu-cores@v1 + id: cpu-cores + + - name: Build + run: | + mkdir ${{github.workspace}}/build + cd ${{github.workspace}}/build + echo CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} + echo CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} + echo ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} + echo ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} + [ ! -d $CASCADE_INC_DIR ] && echo "ERROR: OpenCascade include dir doesn't exist" + [ ! -d $CASCADE_LIB_DIR ] && echo "ERROR: OpenCascade lib dir doesn't exist" + [ ! -d $ASSIMP_INC_DIR ] && echo "ERROR: assimp include dir doesn't exist" + [ ! -d $ASSIMP_LIB_DIR ] && echo "ERROR: assimp lib dir doesn't exist" + qmake .. CONFIG+=withtests \ + CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} \ + CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} \ + ASSIMP_INC_DIR=${{env.GH_ASSIMP_INC_DIR}} \ + ASSIMP_LIB_DIR=${{env.GH_ASSIMP_LIB_DIR}} + make -j${{steps.cpu-cores.outputs.count}} + + - name: Execute Unit Tests + working-directory: ${{github.workspace}}/build + run: | + ./mayo.app/Contents/MacOS/mayo --runtests diff --git a/.github/workflows/ci_windows.yml b/.github/workflows/ci_windows.yml new file mode 100644 index 00000000..de894de8 --- /dev/null +++ b/.github/workflows/ci_windows.yml @@ -0,0 +1,99 @@ +name: Windows + +on: [push] + +jobs: + build-windows-msvc: + strategy: + matrix: + occ_version: [7.3.0, 7.4.0, 7.5.0, 7.6.0, 7.7.0] + + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install Qt + uses: jurplel/install-qt-action@v3 + with: + cache: true + cache-key-prefix: QtCache + + - name: Cache OpenCascade archive + id: cache-occ + uses: actions/cache@v3 + with: + path: OpenCASCADE-${{matrix.occ_version}}-vc14-64.zip + key: occ-${{matrix.occ_version}} + + - name: Download OpenCascade + if: steps.cache-occ.outputs.cache-hit != 'true' + uses: carlosperate/download-file-action@v2 + with: + file-url: 'https://www.fougue.pro/share/bin/OpenCASCADE-${{matrix.occ_version}}-vc14-64.zip' + + - name: Extract OpenCascade + shell: pwsh + run: | + Expand-Archive -Path OpenCASCADE-${{matrix.occ_version}}-vc14-64.zip -DestinationPath . + + - name: Cache Assimp archive + id: cache-assimp + uses: actions/cache@v3 + with: + path: assimp-5.3.1.zip + key: assimp-5.3.1 + + - name: Download Assimp + if: steps.cache-assimp.outputs.cache-hit != 'true' + uses: carlosperate/download-file-action@v2 + with: + file-url: 'https://www.fougue.pro/share/bin/assimp-5.3.1.zip' + + - name: Extract Assimp + shell: pwsh + run: | + Expand-Archive -Path assimp-5.3.1.zip -DestinationPath . + + - name: Download jom.exe + uses: carlosperate/download-file-action@v2 + with: + file-url: 'https://www.fougue.pro/share/bin/jom.exe' + + - name: Get count of CPU cores + uses: SimenB/github-actions-cpu-cores@v1 + id: cpu-cores + + - name: Create Build folder + run: mkdir ${{github.workspace}}/build + + - name: Configure Compiler + uses: ilammy/msvc-dev-cmd@v1 + + - name: QMake + working-directory: ${{github.workspace}}/build + shell: cmd + run: | + call ..\OpenCASCADE-${{matrix.occ_version}}-vc14-64\opencascade-${{matrix.occ_version}}\env.bat + echo CSF_OCCTIncludePath=%CSF_OCCTIncludePath% + echo CSF_OCCTLibPath=%CSF_OCCTLibPath% + qmake --version + qmake ..\mayo.pro CONFIG+=release CONFIG+=withtests ^ + ASSIMP_INC_DIR=${{github.workspace}}/assimp-5.3.1/include/assimp ^ + ASSIMP_LIB_DIR=${{github.workspace}}/assimp-5.3.1/lib ^ + ASSIMP_LIBNAME_SUFFIX=-vc143-mt + + - name: Build + working-directory: ${{github.workspace}}/build + run: | + ..\jom.exe -j${{steps.cpu-cores.outputs.count}} + + - name: Execute Unit Tests + working-directory: ${{github.workspace}}/build + shell: cmd + run: | + call ..\OpenCASCADE-${{matrix.occ_version}}-vc14-64\opencascade-${{matrix.occ_version}}\env.bat + set PATH=${{github.workspace}}\assimp-5.3.1\bin;%PATH% + release\mayo.exe --runtests -o utests-output.txt + more utests-output.txt diff --git a/.gitignore b/.gitignore index 39ce3cf0..96cf762e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ build-* *.user *.user.* -installer/setupvars.iss -installer/Output -custom.pri \ No newline at end of file +custom.pri +env.pri diff --git a/README.md b/README.md index e8051a4e..1fba5424 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@
-[![CI](https://github.com/fougue/mayo/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/fougue/mayo/actions/workflows/ci.yml) -[![Build status](https://ci.appveyor.com/api/projects/status/6d1w0d6gw28npxpf/branch/develop?svg=true)](https://ci.appveyor.com/project/HuguesDelorme/mayo) -[![Codacy Badge](https://app.codacy.com/project/badge/Grade/d51f8ca6fea34886b8308ff0246172ce)](https://www.codacy.com/gh/fougue/mayo/dashboard?utm_source=github.com&utm_medium=referral&utm_content=fougue/mayo&utm_campaign=Badge_Grade) +[![Windows CI](https://github.com/fougue/mayo/actions/workflows/ci_windows.yml/badge.svg?branch=develop)](https://github.com/fougue/mayo/actions/workflows/ci_windows.yml) +[![Linux CI](https://github.com/fougue/mayo/actions/workflows/ci_linux.yml/badge.svg?branch=develop)](https://github.com/fougue/mayo/actions/workflows/ci_linux.yml) +[![macOS CI](https://github.com/fougue/mayo/actions/workflows/ci_macos.yml/badge.svg?branch=develop)](https://github.com/fougue/mayo/actions/workflows/ci_macos.yml) +[![Codacy Badge](https://app.codacy.com/project/badge/Grade/d51f8ca6fea34886b8308ff0246172ce)](https://app.codacy.com/gh/fougue/mayo/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade) [![Downloads](https://img.shields.io/github/downloads/fougue/mayo/total.svg)](https://github.com/fougue/mayo/releases) -[![License](https://img.shields.io/badge/license-BSD%202--clause-blue.svg)](https://github.com/fougue/mayo/blob/develop/LICENSE.txt) [![Version](https://img.shields.io/badge/version-v0.7.0-blue.svg?style=flat)](https://github.com/fougue/mayo/releases)
@@ -62,9 +62,14 @@ OBJ | :white_check_mark: | :white_check_mark: | glTF | :white_check_mark: | :white_check_mark: | 1.0, 2.0 and GLB VRML | :white_check_mark: | :white_check_mark: | v2.0 UTF8 STL | :white_check_mark: | :white_check_mark: | ASCII/binary -AMF | :x: | :white_check_mark: | v1.2 Text/ZIP +AMF | :white_check_mark: | :white_check_mark: | v1.2 Text/ZIP(export) PLY | :white_check_mark: | :white_check_mark: | ASCII/binary OFF | :white_check_mark: | :white_check_mark: | +3MF | :white_check_mark: | :x: | +3DS | :white_check_mark: | :x: | +FBX | :white_check_mark: | :x: | +Collada | :white_check_mark: | :x: | +X3D | :white_check_mark: | :x: | Image | :x: | :white_check_mark: | PNG, JPEG, ... See also this dedicated [wikipage](https://github.com/fougue/mayo/wiki/Supported-formats) for more details diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 4f7157bf..00000000 --- a/appveyor.yml +++ /dev/null @@ -1,63 +0,0 @@ -version: 0.7_build{build} - -image: Visual Studio 2017 -platform: x64 -configuration: Release - -clone_folder: c:\projects\mayo - -#branches: -# only: -# - develop -# - master - -matrix: - fast_finish: true - -environment: - matrix: - - APPVEYOR_OCC_VERSION: 7.3.0 - - APPVEYOR_OCC_VERSION: 7.4.0 - - APPVEYOR_OCC_VERSION: 7.5.0 - - APPVEYOR_OCC_VERSION: 7.6.0 - - APPVEYOR_OCC_VERSION: 7.7.0 - -cache: - - OpenCASCADE-7.3.0-vc14-64.rar - - OpenCASCADE-7.4.0-vc14-64.rar - - OpenCASCADE-7.5.0-vc14-64.rar - - OpenCASCADE-7.6.0-vc14-64.rar - - OpenCASCADE-7.7.0-vc14-64.rar - -install: - - if not exist OpenCASCADE-%APPVEYOR_OCC_VERSION%-vc14-64.rar - appveyor DownloadFile http://www.fougue.pro/share/bin/OpenCASCADE-%APPVEYOR_OCC_VERSION%-vc14-64.rar -FileName OpenCASCADE-%APPVEYOR_OCC_VERSION%-vc14-64.rar - - 7z x OpenCASCADE-%APPVEYOR_OCC_VERSION%-vc14-64.rar - -before_build: - - echo JOB_NAME=%APPVEYOR_JOB_NAME% - - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" - - call "OpenCASCADE-%APPVEYOR_OCC_VERSION%-vc14-64\opencascade-%APPVEYOR_OCC_VERSION%\env.bat" - - set PATH=C:\Qt\5.13\msvc2017_64\bin;%PATH% - - set PATH=C:\Qt\Tools\QtCreator\bin;%PATH% - - qmake --version - - echo NUMBER_OF_PROCESSORS=%NUMBER_OF_PROCESSORS% - -build_script: - - mkdir build-%APPVEYOR_OCC_VERSION% - - cd build-%APPVEYOR_OCC_VERSION% - - qmake ..\mayo.pro CONFIG+=withtests - - jom -j%NUMBER_OF_PROCESSORS% - - cd .. - -test_script: - - cd build-%APPVEYOR_OCC_VERSION% - - release\mayo.exe --runtests - - cd .. - -on_success: - - ps: >- - if ($true) - { - Write-Host "Success" - } diff --git a/doc/screenshot_ubuntu_1.png b/doc/screenshot_ubuntu_1.png new file mode 100644 index 00000000..dd877c88 Binary files /dev/null and b/doc/screenshot_ubuntu_1.png differ diff --git a/doc/screenshot_ubuntu_2.png b/doc/screenshot_ubuntu_2.png new file mode 100755 index 00000000..b57cdc3e Binary files /dev/null and b/doc/screenshot_ubuntu_2.png differ diff --git a/doc/screenshot_ubuntu_3.png b/doc/screenshot_ubuntu_3.png new file mode 100755 index 00000000..ff9e8059 Binary files /dev/null and b/doc/screenshot_ubuntu_3.png differ diff --git a/doc/screenshot_ubuntu_4.png b/doc/screenshot_ubuntu_4.png new file mode 100755 index 00000000..2a0f4070 Binary files /dev/null and b/doc/screenshot_ubuntu_4.png differ diff --git a/doc/screenshot_ubuntu_5.png b/doc/screenshot_ubuntu_5.png new file mode 100755 index 00000000..34ab7e52 Binary files /dev/null and b/doc/screenshot_ubuntu_5.png differ diff --git a/doc/screenshot_ubuntu_main.png b/doc/screenshot_ubuntu_main.png new file mode 100755 index 00000000..9519a13a Binary files /dev/null and b/doc/screenshot_ubuntu_main.png differ diff --git a/i18n/mayo_en.qm b/i18n/mayo_en.qm index 2bd1c216..d83aed12 100644 Binary files a/i18n/mayo_en.qm and b/i18n/mayo_en.qm differ diff --git a/i18n/mayo_en.ts b/i18n/mayo_en.ts index 43b76ad8..5fcd5f1a 100644 --- a/i18n/mayo_en.ts +++ b/i18n/mayo_en.ts @@ -28,6 +28,33 @@ User Defined + + AppModuleProperties + + VeryCoarse + Very Coarse + + + Coarse + Coarse + + + Normal + Normal + + + Precise + Precise + + + VeryPrecise + Very Precise + + + UserDefined + User Defined + + Mayo::AppModule @@ -157,34 +184,28 @@ Export - VeryCoarse - Very Coarse + Very Coarse - Coarse - Coarse + Coarse - Normal - Normal + Normal - Precise - Precise + Precise - VeryPrecise - Very Precise + Very Precise - UserDefined - User Defined + User Defined @@ -240,17 +261,17 @@ Export - + Language used for the application. Change will take effect after application restart - + In case where multiple documents are opened, make sure the document displayed in the 3D view corresponds to what is selected in the model tree - + Force usage of the fallback Qt widget to display OpenGL graphics. When `OFF` the application will try to use OpenGL framebuffer for rendering, this allows to display overlay widgets(eg measure tools panel) with translucid background. However using OpenGL framebuffer might cause troubles for some users(eg empty 3D window) especially on macOS. @@ -261,49 +282,49 @@ This option is applicable when OpenCascade ≥ 7.6 version. Change will take eff - + Controls precision of the mesh to be computed from the BRep shape - + For the tessellation of faces the chordal deflection limits the distance between a curve and its tessellation - + For the tessellation of faces the angular deflection limits the angle between subsequent segments in a polyline - + Relative computation of edge tolerance If activated, deflection used for the polygonalisation of each edge will be `ChordalDeflection` &#215; `SizeOfEdge`. The deflection used for the faces will be the maximum deflection of their edges. - + 3D view manipulation shortcuts configuration to mimic other common CAD applications - + Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference) - + Show or hide by default the trihedron centered at world origin. This doesn't affect 3D view of currently opened documents - + Enable capping of currently clipped graphics - + Enable capping hatch texture of currently clipped graphics @@ -417,18 +438,58 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho showNodesOn Show Nodes + + + SI + + + + + ImperialUK + Imperial UK + + + + VeryCoarse + Very Coarse + + + + Coarse + Coarse + + + + Normal + Normal + + + + Precise + Precise + + + + VeryPrecise + Very Precise + + + + UserDefined + User Defined + Mayo::Application Binary Mayo Document Format - + XML Mayo Document Format - + @@ -462,214 +523,218 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::Command - + Orthographic - + Perspective - + Projection - + Mode - + Show Origin Trihedron - + Show/Hide Origin Trihedron - + Show Performance Stats - + Show/Hide rendering performance statistics - + Zoom In - + Zoom Out - + Turn Counter Clockwise - + - + Turn Clockwise - + - + %1 files(%2) %1 is the format identifier and %2 is the file filters string - + All files(*.*) - + Select Part File - - + + Mesh BRep shapes - - + + Import time: {}ms - + New - + New Document - + Anonymous%1 - + Open - + Open Documents - + Recent files - + %1 | %2 - + Clear menu - - + + Import Import - + Import in current document - - + + Export selected items - + No item selected for export - + - + Select Output File - + Export time: {}ms - - + Close "%1" - + + Close %1 + + + + Close - + Close all - + Close all documents - - + + Close all except current - + Close all except current document - - Close all except "%1" + + Close all except %1 - + Quit - + Report Bug - + About %1 @@ -680,29 +745,34 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - + Inspect XDE - - + + Options - + Fullscreen - + Switch Fullscreen/Normal - + + Hide Left Sidebar + + + + Show Left Sidebar @@ -712,17 +782,38 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - - + + + Go To Home Page + + + + + Go To Documents + + + + + Previous Document - - + + Next Document + + + System Information... + + + + + Copy to Clipboard + + Mayo::DialogAbout @@ -772,82 +863,82 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho XDE - + ShapeType=%1, Evolution=%2 - + Yes - + No - + File Size: %1<br>Dimensions: %2x%3 Depth: %4 - + Error when loading texture file(invalid path?) - + - + Shape Shape - + Color Color - + Material - + - + VisMaterial - + - + Dimension - + - + Datum - + - + GeomTolerance - + - + Error - + - + This document is not suitable for XDE - + - + Attributes - + @@ -855,76 +946,76 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Options - + Restore default values - + %1 / %2 - + - + Exchange - + - + Load from file... - + - + Save as... - + - - + + Choose INI file - + - - + + INI files(*.ini) - + - - - + + + Error - + - + '%1' doesn't exist - + - + '%1' is not readable - + - + Error when writing to'%1' - + - + Restore values for default section only - + - + Restore values for the whole group - + @@ -932,79 +1023,79 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Save View to Image - + Options - + Width - + px - + Height - + Keep ratio - + Save - + Copy - + Preview - + %1 files(*.%2) - + Select image file - + Error - + Failed to save image '%1' - + %1x%2 %3 - + Free ratio - + @@ -1012,43 +1103,43 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Tasks - + / - + Mayo::DocumentPropertyGroup - + filepath File Path - + fileSize File Size - + createdDateTime Created - + modifiedDateTime Modified - + owner Owner - + entityCount Count Of Entities @@ -1169,22 +1260,22 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Scale entities according some factor - + Import text/dimension objects - + Group all objects within a layer into a single compound shape - + Name of the font to be used when creating shape for text objects - + @@ -1212,44 +1303,44 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Decimal floating point(ex: 392.65) - + Scientific notation(ex: 3.9265E+2) - + Use the shortest representation: decimal or scientific - + Format used when writing `double` values as strings - + Maximum number of significant digits when writing `double` values - + Write AMF document in ZIP archive containing one file entry - + Filename of the single AMF entry within the ZIP archive. Only applicable if option `{}` is on - + Use the ZIP64 format extensions. Only applicable if option `{}` is on - + @@ -1276,6 +1367,21 @@ Only applicable if option `{}` is on useZip64 Use ZIP64 extensions + + + Decimal + + + + + Scientific + + + + + Shortest + + Mayo::IO::ImageWriter::Properties @@ -1305,52 +1411,62 @@ Only applicable if option `{}` is on Image width in pixels - + Image height in pixels - + Camera orientation expressed in Z-up convention as a unit vector - + - + width Width - + height Height - + backgroundColor Background Color - + cameraOrientation Camera Orientation - + cameraProjection Camera Projection - + No transferred application items - + - + Camera orientation vector must not be null - + + + + + Perspective + + + + + Orthographic + @@ -1384,60 +1500,60 @@ Only applicable if option `{}` is on Mayo::IO::OccCommon - - + + Undefined - + - + posYfwd_posZup - + +Zup - + negZfwd_posYup - + +Yup - + Micrometer - + - + Millimeter - + - + Centimeter - + - + Meter - + - + Kilometer - + - + Inch - + - + Foot - + - + Mile - + @@ -1455,12 +1571,12 @@ Only applicable if option `{}` is on Ignore nodes without geometry(`Yes` by default) - + Use mesh name in case if node name is empty(`Yes` by default) - + @@ -1470,129 +1586,139 @@ Only applicable if option `{}` is on Coordinates Converter - + transformationFormat Transformation Format - + format - Format + Target Format - + forceExportUV Force UV Export Source coordinate system transformation - + Target coordinate system transformation - + Preferred transformation format for writing into glTF file - + Export UV coordinates even if there is no mapped texture - + Automatically choose most compact representation between Mat4 and TRS - + 4x4 transformation matrix - + Transformation decomposed into Translation vector, Rotation quaternion and Scale factor(T * R * S) - + - + Name format for exporting nodes - + - + Name format for exporting meshes - + - + Write image textures into target file. If set to `false` then texture images will be written as separate files. Applicable only if option `{0}` is set to `{1}` - + - + Merge faces within a single part. May reduce JSON size thanks to smaller number of primitive arrays - + - + Prefer keeping 16-bit indexes while merging face. May reduce binary data size thanks to smaller triangle indexes. Applicable only if option `{}` is on - + - + inputCoordinateSystem - + Input Coordinate System - + outputCoordinateSystem - + Output Coordinate System - + nodeNameFormat Node Name Format - + meshNameFormat Mesh Name Format - + embedTextures Embed Textures - + mergeFaces Merge Faces - + keepIndices16b Keep 16bit Indices - + Option supported from OpenCascade ≥ v7.6 [option={}, actual version={}] - + + + + + Json + JSON + + + + Binary + Binary @@ -1620,7 +1746,7 @@ Applicable only if option `{}` is on Manages the continuity of BSpline curves (IGES entities 106, 112 and 126) after translation to Open CASCADE (it requires that the curves in a model be at least C1 continuous; no such requirement is made by IGES).This parameter does not change the continuity of curves that are used in the construction of IGES BRep entities. In this case, the parameter does not influence the continuity of the resulting Open CASCADE curves (it is ignored). - + @@ -1631,52 +1757,52 @@ Concerned entity types are 141 (Boundary), 142 (CurveOnSurface) and 508 (Loop). The processor also decides to re-compute either the 3D or the 2D curve even if both curves are translated successfully and seem to be correct, in case there is inconsistency between them. The processor considers that there is inconsistency if any of the following conditions is satisfied: - the number of sub-curves in the 2D curve is different from the number of sub-curves in the 3D curve. This can be either due to different numbers of sub-curves given in the IGES file or because of splitting of curves during translation - 3D or 2D curve is a Circular Arc (entity type 100) starting and ending in the same point (note that this case is incorrect according to the IGES standard) - + Read failed entities - + Curves are taken as they are in the IGES file. C0 entities of Open CASCADE may be produced - + If an IGES BSpline, Spline or CopiousData curve is C0 continuous, it is broken down into pieces of C1 continuous Geom_BSplineCurve - + IGES Spline curves are broken down into pieces of C2 continuity. If C2 cannot be ensured, the Spline curves will be broken down into pieces of C1 continuity - + Use the preference flag value in the entity's `Parameter Data` section - + The 2D is used to rebuild the 3D in case of their inconsistency - + The 2D is always used to rebuild the 3D (even if 3D is present in the file) - + The 3D is used to rebuild the 2D in case of their inconsistency - + The 3D is always used to rebuild the 2D (even if 2D is present in the file) - + @@ -1699,17 +1825,17 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Indicates if planes should be saved as Bsplines or Planes (type 108). Writing p-curves on planes is disabled - + OpenCascade TopoDS_Faces will be translated into IGES 144 (Trimmed Surface) entities, no BRep entities will be written to the IGES file - + OpenCascade TopoDS_Faces will be translated into IGES 510 (Face) entities, the IGES file will contain BRep entities - + @@ -1722,7 +1848,7 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Single precision flag for reading vertex data(coordinates) - + @@ -1734,22 +1860,22 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Source coordinate system transformation - + Target coordinate system transformation - + inputCoordinateSystem - + Input Coordinate System outputCoordinateSystem - + Output Coordinate System @@ -1788,83 +1914,83 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b When reading AP 209 STEP files, allows selecting either only `design` or `analysis`, or both types of products for translation Note that in AP 203 and AP214 files all products should be marked as `design`, so if this mode is set to `analysis`, nothing will be read - + Specifies which data should be read for the products found in the STEP file - + Specifies preferred type of representation of the shape of the product, in case if a STEP file contains more than one representation (i.e. multiple `PRODUCT_DEFINITION_SHAPE` entities) for a single product - + Defines whether shapes associated with the `PRODUCT_DEFINITION_SHAPE` entity of the product via `SHAPE_ASPECT` should be translated. This kind of association was used for the representation of hybrid models (i.e. models whose shape is composed of different types of representations) in AP 203 files before 1998, but it is also used to associate auxiliary information with the sub-shapes of the part. Though STEP translator tries to recognize such cases correctly, this parameter may be useful to avoid unconditionally translation of shapes associated via `SHAPE_ASPECT` entities. - + Indicates whether to read sub-shape names from 'Name' attributes of STEP Representation Items - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `design` - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `analysis` - + Translates all products - + Translate the assembly structure and shapes associated with parts only(not with sub-assemblies) - + Translate only the assembly structure without shapes(a structure of empty compounds). This mode can be useful as an intermediate step in applications requiring specialized processing of assembly parts - + Translate only shapes associated with the product, ignoring the assembly structure (if any). This can be useful to translate only a shape associated with specific product, as a complement to assembly mode - + Translate both the assembly structure and all associated shapes. If both shape and sub-assemblies are associated with the same product, all of them are read and put in a single compound - + Translate all representations(if more than one, put in compound) - + Shift Japanese Industrial Standards - + EUC(Extended Unix Code), multi-byte encoding primarily for Japanese, Korean, and simplified Chinese - + GB(Guobiao) encoding for Simplified Chinese - + @@ -1922,58 +2048,58 @@ This kind of association was used for the representation of hybrid models (i.e. Version of schema used for the output STEP file - + Defines a unit in which the STEP file should be written. If set to unit other than millimeter, the model is converted to these units during the translation - + Parameter to write all free vertices in one SDR (name and style of vertex are lost) or each vertex in its own SDR (name and style of vertex are exported) - + All free vertices are united into one compound and exported in one shape definition representation (vertex name and style are lost) - + Each vertex is exported in its own `SHAPE DEFINITION REPRESENTATION`(vertex name and style are not lost, but the STEP file size increases) - + Indicates whether parametric curves (curves in parametric space of surface) should be written into the STEP file. It can be disabled in order to minimize the size of the resulting file. - + Indicates whether to write sub-shape names to 'Name' attributes of STEP Representation Items - + Author attribute in STEP header - + Organization(of author) attribute in STEP header - + Originating system attribute in STEP header - + Description attribute in STEP header - + @@ -1983,28 +2109,36 @@ It can be disabled in order to minimize the size of the resulting file. Target Format - Ascii - Text + Text - Binary - Binary + Binary Mayo::IO::OccStlWriterI18N - + targetFormat - Target Format + Target Format - - + + Not all BRep faces are meshed - + + + + + Ascii + Text + + + + Binary + Binary @@ -2052,171 +2186,186 @@ It can be disabled in order to minimize the size of the resulting file. Unknown host endianness + + + Ascii + Text + + + + Binary + Binary + Mayo::IO::System Reading file - + Unknown format - + Error during import of '{}' {} - + No supporting reader - + File read problem - + Transferring file - + File transfer problem - + Error during export to '{}' {} - + No supporting writer - + Transfer - + Write - + File write problem - + Mayo::Main - + Theme for the UI(classic|dark) - + - + name - + - + Writes log messages into output file - + - + Don't filter out debug log messages in release build - + - + Disable progress reporting in console output(CLI-mode only) - + - + + Show detailed system information and quit + + + + files - + - + Files to open at startup, optionally - + - + [files...] - + - + Execute unit tests and exit application - + - + OpenCascade settings file doesn't exist or is not readable [path=%1] - + - + OpenCascade settings file could not be loaded with QSettings [path=%1] - + - + Failed to load translation file [path=%1] - + - + Settings file(INI format) to load at startup - + - + Mayo the opensource 3D CAD viewer and converter - + - - - + + + filepath File Path - + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) - + - + Failed to load application settings file [path=%1] - + - + No input files -> nothing to export - + - + Failed to load theme '%1' - + @@ -2224,105 +2373,47 @@ It can be disabled in order to minimize the size of the resulting file. Mayo - - - - - Model tree - - - - - Opened documents - - - - - File system - - - - - Close Left Side Bar - - - - - X= - - - - - - - ? - - - - - Y= - - - - - Z= - + - + &File - + - + &Help - + - + &Tools - + - + &Window - + - + &Display - + Import Import - - Options - - - - + Warning - + - - + + Error - - - - - - Data - - - - - Graphics - + @@ -2383,7 +2474,7 @@ It can be disabled in order to minimize the size of the resulting file. Choose color ... - + @@ -2391,33 +2482,33 @@ It can be disabled in order to minimize the size of the resulting file. %1d - + %1h - + %1min - + %1s - + %1%2 - + ERROR no stringifier for property type '%1' - + @@ -2426,12 +2517,12 @@ It can be disabled in order to minimize the size of the resulting file. (%1 %2 %3) - + [%1; %2%3; %4] - + @@ -2440,41 +2531,41 @@ It can be disabled in order to minimize the size of the resulting file. %1%2 - + B - + KB - + MB - + Yes - + No - + Partially - + @@ -2482,12 +2573,12 @@ It can be disabled in order to minimize the size of the resulting file. Edit clip planes - + X plane - + @@ -2495,7 +2586,7 @@ It can be disabled in order to minimize the size of the resulting file. Reverse plane - + @@ -2503,37 +2594,37 @@ It can be disabled in order to minimize the size of the resulting file. +/- - + Y plane - + Z plane - + Custom - + X - + Y - + Z - + @@ -2541,12 +2632,12 @@ It can be disabled in order to minimize the size of the resulting file. Form - + % - + @@ -2556,71 +2647,230 @@ It can be disabled in order to minimize the size of the resulting file. %1 Size: %2 Last modified: %3 - + + + + + Mayo::WidgetGrid + + + Form + + + + + Show Grid + + + + + Plane: XOY + + + + + Plane: ZOX + + + + + Plane: YOZ + + + + + Plane: Custom + + + + + Configuration + + + + + Type + + + + + Rectangular + + + + + Circular + + + + + + + + Y + + + + + Step + + + + + + + + X + + + + + Size + + + + + + Rotation + + + + + + ° + + + + + + Offset + + + + + + Origin + + + + + Radius + + + + + Radius Step + + + + + Division + + + + + Graphics + + + + + + ... + + + + + Tenth Color + + + + + Mode + + + + + Lines + + + + + Points + + + + + Color + Color Mayo::WidgetGuiDocument - + Fit All - + - + + Edit Grid + + + + Edit clip planes - + - + Explode assemblies - + - + Measure shapes - + - + Isometric - + - + Back - + - + Front - + - + Left - + - + Right - + - + Top - + - + Bottom - + - + <b>Left-click</b>: popup menu of pre-defined views <b>CTRL+Left-click</b>: apply '%1' view - + @@ -2628,47 +2878,47 @@ Last modified: %3 New Document - + Create and add an empty document where you can import files - + Open Document(s) - + Select files to load and open as distinct documents - + today %1 - + yersterday %1 - + %1 %2 - + %1 days ago %2 - + @@ -2680,7 +2930,81 @@ Created: %3 Modified: %4 Read: %5 - + + + + + Mayo::WidgetMainControl + + + Form + + + + + Model tree + + + + + Opened documents + + + + + File system + + + + + Close Left Side Bar + + + + + X= + + + + + + + ? + + + + + Y= + + + + + Z= + + + + + + Data + + + + + Graphics + + + + + Options + + + + + Mayo::WidgetMainHome + + + Form + @@ -2688,137 +3012,142 @@ Read: %5 Form - + Area Unit - + Measure - + Millimeter(mm) - + Centimeter(cm) - + Meter(m) - + Inch(in) - + Foot(ft) - + Yard(yd) - + Degree(°) - + Radian(rad) - + Vertex Position - + Circle Center - + Circle Diameter - + Min Distance - + - + + Center-to-center Distance + + + + Length - + - + Square Millimeter(mm²) - + - + Square Centimeter(cm²) - + - + Square Meter(m²) - + - + Square Inch(in²) - + - + Square Foot(ft²) - + - + Square Yard(yd²) - + - + Angle - + - + Surface Area - + Length Unit - + Angle Unit - + - + Select entities to measure - + @@ -2826,17 +3155,17 @@ Read: %5 Form - + Remove from document - + <unnamed> - + @@ -2849,22 +3178,22 @@ Read: %5 Show {} - + - + Instance - + - + Product - + - + Both - + @@ -2872,17 +3201,17 @@ Read: %5 Form - + Property - + Value - + @@ -2994,6 +3323,17 @@ Read: %5 OccStlWriter::Properties + + Ascii + Text + + + Binary + Binary + + + + OccStlWriterI18N Ascii Text @@ -3076,102 +3416,102 @@ Read: %5 Brass - + Bronze - + Copper - + Gold - + Pewter - + Plaster - + Plastic - + Silver - + Steel - + Stone - + ShinyPlastic - + Satin - + Metalized - + NeonGnc - + Chrome - + Aluminium - + Obsidian - + NeonPhc - + Jade - + Default - + diff --git a/i18n/mayo_fr.qm b/i18n/mayo_fr.qm index d0002376..dbaa78ea 100644 Binary files a/i18n/mayo_fr.qm and b/i18n/mayo_fr.qm differ diff --git a/i18n/mayo_fr.ts b/i18n/mayo_fr.ts index 502e1072..3a75a448 100644 --- a/i18n/mayo_fr.ts +++ b/i18n/mayo_fr.ts @@ -28,6 +28,33 @@ Custom + + AppModuleProperties + + VeryCoarse + Très grossière + + + Coarse + Grossière + + + Normal + Normale + + + Precise + Précise + + + VeryPrecise + Très précise + + + UserDefined + Custom + + Mayo::AppModule @@ -197,34 +224,28 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Export - VeryCoarse - Très grossière + Très grossière - Coarse - Grossière + Grossière - Normal - Normale + Normale - Precise - Précise + Précise - VeryPrecise - Très précise + Très précise - UserDefined - Custom + Custom @@ -280,17 +301,17 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Export - + Language used for the application. Change will take effect after application restart Langage de l'application. Tout changement sera effectif après redémarrage de l'application - + In case where multiple documents are opened, make sure the document displayed in the 3D view corresponds to what is selected in the model tree Dans le cas où plusieurs documents sont ouverts, fait en sort que le document affiché dans la vue 2D correspond à ce qui est sélectionné dans l'arborescence du modèle - + Force usage of the fallback Qt widget to display OpenGL graphics. When `OFF` the application will try to use OpenGL framebuffer for rendering, this allows to display overlay widgets(eg measure tools panel) with translucid background. However using OpenGL framebuffer might cause troubles for some users(eg empty 3D window) especially on macOS. @@ -307,17 +328,17 @@ Quand l'option est activée alors l'application utilisera un widget Qt Cette option est appliquable seulement avec la version ≥ 7.6 d'OpenCascade. Tout changement sera effectif après redémarrage de l'application - + Controls precision of the mesh to be computed from the BRep shape Contrôle la précision du maillage calculé à partir de la forme BRep - + For the tessellation of faces the chordal deflection limits the distance between a curve and its tessellation Pour la tesselation des faces, la déflection chordale limite la distance entre une courbe et sa discrétisation - + For the tessellation of faces the angular deflection limits the angle between subsequent segments in a polyline Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne @@ -330,7 +351,7 @@ Cette option est appliquable seulement avec la version ≥ 7.6 d'OpenCascad Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne - + Relative computation of edge tolerance If activated, deflection used for the polygonalisation of each edge will be `ChordalDeflection` &#215; `SizeOfEdge`. The deflection used for the faces will be the maximum deflection of their edges. @@ -339,27 +360,27 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Si actif, la déflection utilisée pour la polygonisation de chaque arête sera de `DéflectionChordale` &#215; `TailleArête`. La déflection utilisée pour les faces sera la déflection maximale de ses arêtes. - + 3D view manipulation shortcuts configuration to mimic other common CAD applications Configuration des raccourcis pour manipuler la vue 3D, permet d'imiter les autres application CAO - + Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference) Incrément angulaire utilisé pour tourner la vue 3D autour de la normale au plan de vue (axe Z de référence) - + Show or hide by default the trihedron centered at world origin. This doesn't affect 3D view of currently opened documents Montrer/cacher par défaut le trièdre positionné à l'orgine "monde". N'affecte pas la vue 3D des documents actuellement ouverts - + Enable capping of currently clipped graphics Activer le bouchage des graphismes actuellement coupés - + Enable capping hatch texture of currently clipped graphics Activer le hachage texturé pour le bouchage des graphismes actuellement coupés @@ -473,6 +494,46 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera showNodesOn Afficher les nœuds + + + SI + SI + + + + ImperialUK + Système impérial + + + + VeryCoarse + Très grossière + + + + Coarse + Grossière + + + + Normal + Normale + + + + Precise + Précise + + + + VeryPrecise + Très précise + + + + UserDefined + Custom + Mayo::Application @@ -561,216 +622,224 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::Command - + Orthographic Orthographique - + Perspective - + Projection - + Mode Mode - + Show Origin Trihedron Montrer le trihèdre Origine - + Show/Hide Origin Trihedron Montrer/cacher le trihèdre Origine - + Show Performance Stats Montrer les statistiques de rendu - + Show/Hide rendering performance statistics Montrer/cacher les statistiques de rendu - + Zoom In Zoom avant - + Zoom Out Zoom arrière - + Turn Counter Clockwise Tourner dans le sens anti-horaire - + Turn Clockwise Tourner dans le sens horaire - + %1 files(%2) %1 is the format identifier and %2 is the file filters string %1 fichiers (%2) - + All files(*.*) Tous les fichiers (*.*) - + Select Part File Selectionner fichier pièce - - + + Mesh BRep shapes Maillage des formes BRep - - + + Import time: {}ms Durée import: {}ms - + New Nouveau - + New Document Nouveau Document - + Anonymous%1 Anonyme%1 - + Open Ouvrir - + Open Documents Ouvrir des documents - + Recent files Fichiers récents - + %1 | %2 - + Clear menu Vider le menu - - + + Import Importer - + Import in current document Importer dans le document courant - - + + Export selected items Exporter les éléments sélectionnées - + No item selected for export Aucun élément sélectionné pour l'export - + Select Output File Sélection fichier de sortie - + Export time: {}ms Durée export: {}ms - - + Close "%1" Fermer "%1" - + + Close %1 + Fermer %1 + + + Close Fermer - + Close all Tout fermer - + Close all documents Fermer tous les documents - - + + Close all except current Tout fermer sauf document courant - + Close all except current document Tout fermer sauf document courant - + + Close all except %1 + Tout fermer sauf %1 + + Close all except "%1" - Tout fermer sauf "%1" + Tout fermer sauf "%1" - + Quit Quitter - + Report Bug Signaler un bug - + About %1 - À propos %1 + À propos de %1 @@ -779,29 +848,34 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Sauvegarder la vue vers une image - + Inspect XDE Inspection XDE - - + + Options Options - + Fullscreen Plein-écran - + Switch Fullscreen/Normal Basculer plein-écran/normal - + + Hide Left Sidebar + Cacher le bandeau vertical fixé à gauche + + + Show Left Sidebar Montrer le bandeau vertical fixé à gauche @@ -811,17 +885,38 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Montrer/cacher le bandeau vertical fixé à gauche - - + + + Go To Home Page + Aller à la page d'accueil + + + + Go To Documents + Aller à la page des documents + + + + Previous Document Document précédent - - + + Next Document Document suivant + + + System Information... + Informations du système ... + + + + Copy to Clipboard + Copier dans le presse-papiers + Mayo::CommandCloseCurrentDocument @@ -910,52 +1005,52 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera - + Shape Forme - + Color Couleur - + Material - + VisMaterial - + Dimension - + Datum - + GeomTolerance - + Error Erreur - + This document is not suitable for XDE Ce document n'est pas XDE-compatible - + Attributes Attributs @@ -978,61 +1073,61 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera %1 / %2 - + Exchange Échanger - + Load from file... Charger le fichier ... - + Save as... Sauvergarder vers ... - - + + Choose INI file Choisir fichier INI - - + + INI files(*.ini) Fichiers INI(*.ini) - - - + + + Error Erreur - + '%1' doesn't exist '%1' n'existe pas - + '%1' is not readable '%1' ne dispose pas des permissions de lecture - + Error when writing to'%1' Erreur lors de l'écriture vers '%1' - + Restore values for default section only Restaurer les valeurs seulement pour la section par défaut - + Restore values for the whole group Restaure les valeurs pour tout le groupe @@ -1133,32 +1228,32 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::DocumentPropertyGroup - + filepath Chemin - + fileSize Taille - + createdDateTime Créé - + modifiedDateTime Modifié - + owner Propriétaire - + entityCount Nombre d'entités @@ -1412,6 +1507,21 @@ Seulement applicable si l'option `%1` est activée useZip64 Utiliser les extensions ZIP64 + + + Decimal + Décimal + + + + Scientific + Scientifique + + + + Shortest + Le plus bref + Mayo::IO::ImageWriter::Properties @@ -1466,40 +1576,50 @@ Seulement applicable si l'option `%1` est activée Orientation de la caméra selon la convention Z-up exprimée en tant que vecteur unitaire - + width Largeur - + height Hauteur - + backgroundColor Couleur de l'arrière plan - + cameraOrientation Orientation de la caméra - + cameraProjection Projection de la caméra - + No transferred application items Aucun élément transféré - + Camera orientation vector must not be null Le vecteur d'orienation de la caméra ne doit pas être nul + + + Perspective + + + + + Orthographic + Orthographique + Mayo::IO::OccBaseMeshReaderProperties @@ -1521,69 +1641,69 @@ Seulement applicable si l'option `%1` est activée Prefix for generating root labels name - + Préfixe pour la génération des noms de labels racine System length units to convert into while reading files - + Système d'unité de longueur cible lors de la lecture fichier Mayo::IO::OccCommon - - + + Undefined Indéfini - + posYfwd_posZup +Zup - + negZfwd_posYup +Yup - + Micrometer Micromètre - + Millimeter Millimètre - + Centimeter Centimètre - + Meter Mètre - + Kilometer Kilomètre - + Inch Pouce - + Foot Pied - + Mile Mile @@ -1622,17 +1742,17 @@ Seulement applicable si l'option `%1` est activée Convertisseur de coordonnées - + transformationFormat Format de transformation - + format - Format + Format cible - + forceExportUV Forcer l'export UV @@ -1676,17 +1796,17 @@ Seulement applicable si l'option `%1` est activée Transformation décomposée en vecteur de translation, quaternion de rotation et facteur d'échelle (T x R x S) - + Name format for exporting nodes Format du nom utilisé pour exporter la hiérarchie de nœuds - + Name format for exporting meshes Format du nom utilisé pour exporter la hiérarchie de maillages - + Write image textures into target file. If set to `false` then texture images will be written as separate files. @@ -1711,7 +1831,7 @@ If set to `false` then texture images will be written as separate files. Applicable only if option `{}` is set to `{}` - + Merge faces within a single part. May reduce JSON size thanks to smaller number of primitive arrays @@ -1720,7 +1840,7 @@ May reduce JSON size thanks to smaller number of primitive arrays Peut réduire la taille JSON grâce à une quantité réduite de tableaux de primitives - + Prefer keeping 16-bit indexes while merging face. May reduce binary data size thanks to smaller triangle indexes. @@ -1733,45 +1853,55 @@ Peut réduite la taille des données grâce à une quantité réduite d'ind Applicable seulement si l'option `{}` est cochée - + inputCoordinateSystem Système de coordonnées d'entrée - + outputCoordinateSystem Système de coordonnées de sortie - + nodeNameFormat Format du nom pour les nœuds - + meshNameFormat Format du nom pour les maillages - + embedTextures Incorporer les textures dans le même fichier cible - + mergeFaces Fusionner les faces - + keepIndices16b Utiliser des indices 16bit - + Option supported from OpenCascade ≥ v7.6 [option={}, actual version={}] Option prise en charge à partir de OpenCascade ≥ v7.6 [option={}, version actuelle={}] + + + Json + JSON + + + + Binary + Binaire + Mayo::IO::OccIgesReader::Properties @@ -1814,7 +1944,7 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Read failed entities - + Lecture des entités en erreur @@ -2161,29 +2291,37 @@ It can be disabled in order to minimize the size of the resulting file. Format cible - Ascii - Texte + Texte - Binary - Binaire + Binaire Mayo::IO::OccStlWriterI18N - + targetFormat Format cible - - + + Not all BRep faces are meshed Les faces BRep ne sont pas toutes maillées + + + Ascii + Texte + + + + Binary + Binaire + Mayo::IO::OccVrmlWriter::Properties @@ -2230,6 +2368,20 @@ It can be disabled in order to minimize the size of the resulting file. Unknown host endianness Boutisme du CPU inconnu + + Json + JSON + + + + Ascii + Texte + + + + Binary + Binaire + Mayo::IO::System @@ -2323,62 +2475,67 @@ It can be disabled in order to minimize the size of the resulting file. Mayo, une visionneuse 3D en code libre basée surQt5/OpenCascade - + Theme for the UI(classic|dark) Thème de l'IHM (classic|dark) - + name nom - + Writes log messages into output file Écrit les messages de log dans un fichier de sortie - + Don't filter out debug log messages in release build Ne pas filtrer les messages de debug dans la version "release" - + Disable progress reporting in console output(CLI-mode only) Désactiver l'indicateur de progression dans la sortie console (mode CLI seulement) - + + Show detailed system information and quit + Montrer les informations détaillées du système puis quitter + + + files files - + Files to open at startup, optionally Fichiers à ouvrir au démarrage, optionnel - + [files...] [fichiers ...] - + Execute unit tests and exit application Exécuter les tests unitaires et quitter l'application - + OpenCascade settings file doesn't exist or is not readable [path=%1] Le fichier de configuration OpenCascade n'existe pas ou non lisible [chemin=%1] - + OpenCascade settings file could not be loaded with QSettings [path=%1] Le fichier de configuration OpenCascade n'a pu être chargé par QSettings [chemin=%1] - + Failed to load translation file [path=%1] Échec chargement du fichier de traductions [chemin=%1] @@ -2391,7 +2548,7 @@ It can be disabled in order to minimize the size of the resulting file. Export de {} en cours ... - + Failed to load application settings file [path=%1] Échec chargement du fichier de configuration [chemin=%1] @@ -2402,24 +2559,24 @@ It can be disabled in order to minimize the size of the resulting file. - + Settings file(INI format) to load at startup Fichier de configuration (format INI) à charger au démarrage - + Mayo the opensource 3D CAD viewer and converter Mayo le visualiseur et convertisseur 3D pour la CAO - - - + + + filepath - + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) Exporter des fichiers dans un fichier de sortie, répétable selon les différents formats supportés (par exemple -e file.stp -e file.igs ...) @@ -2444,12 +2601,12 @@ It can be disabled in order to minimize the size of the resulting file. Export de %1 en cours ... - + No input files -> nothing to export Auncun fichier en entrée -> aucun export - + Failed to load theme '%1' Impossible de charger le thème '%1' @@ -2470,69 +2627,59 @@ It can be disabled in order to minimize the size of the resulting file. - Model tree - Arborescence Modèle + Arborescence Modèle - Opened documents - Documents ouverts + Documents ouverts - File system - Système de fichiers + Système de fichiers - Close Left Side Bar - Fermer la barre à gauche + Fermer la barre à gauche - X= - X= + X= - - - ? - ? + ? - Y= - Y= + Y= - Z= - Z= + Z= - + &File &Fichier - + &Help &Aide - + &Tools &Outils - + &Window F&enêtre - + &Display A&ffichage @@ -2560,11 +2707,6 @@ It can be disabled in order to minimize the size of the resulting file. Report Bug Signaler un bug - - - Options - - Save View to Image Sauvegarder la vue vers une image @@ -2655,13 +2797,13 @@ It can be disabled in order to minimize the size of the resulting file. Selectionner fichier pièce - + Warning Avertissement - - + + Error Erreur @@ -2698,15 +2840,12 @@ It can be disabled in order to minimize the size of the resulting file. Temps export : %1ms - - Data - Données + Données - Graphics - Graphismes + Graphismes Close %1 @@ -2992,12 +3131,12 @@ It can be disabled in order to minimize the size of the resulting file. Form - Form + Form % - + @@ -3014,65 +3153,224 @@ Modifié le: %3 {1 ?} + + Mayo::WidgetGrid + + + Form + Form + + + + Show Grid + Afficher grille + + + + Plane: XOY + Plan : XOY + + + + Plane: ZOX + Plan : ZOX + + + + Plane: YOZ + Plan :YOZ + + + + Plane: Custom + Plan : personnalisé + + + + Configuration + Configuration + + + + Type + + + + + Rectangular + Rectangulaire + + + + Circular + Circulaire + + + + + + + Y + + + + + Step + Pas + + + + + + + X + + + + + Size + Taille + + + + + Rotation + + + + + + ° + + + + + + Offset + Décalage + + + + + Origin + Origine + + + + Radius + Rayon + + + + Radius Step + Pas du rayon + + + + Division + + + + + Graphics + Graphismes + + + + + ... + + + + + Tenth Color + Couleur des dizaines + + + + Mode + Mode + + + + Lines + Lignes + + + + Points + Points + + + + Color + Couleur + + Mayo::WidgetGuiDocument - + Fit All Adapter à tout - + + Edit Grid + Éditer la grille + + + Edit clip planes Éditer les plans de coupe - + Explode assemblies Éclater l'assemblage - + Measure shapes Mesures - + Isometric Isométrique - + Back Arrière - + Front Devant - + Left Gauche - + Right Droit - + Top Haut - + Bottom Bas - + <b>Left-click</b>: popup menu of pre-defined views <b>CTRL+Left-click</b>: apply '%1' view <b>Click gauche</b> : menu déroulant des vues pré-définies @@ -3150,12 +3448,86 @@ Lu: %5 + + Mayo::WidgetMainControl + + + Form + Form + + + + Model tree + Arborescence Modèle + + + + Opened documents + Documents ouverts + + + + File system + Système de fichiers + + + + Close Left Side Bar + Fermer le bandeau vertical fixé à gauche + + + + X= + X= + + + + + + ? + ? + + + + Y= + Y= + + + + Z= + Z= + + + + + Data + Données + + + + Graphics + Graphismes + + + + Options + Options + + + + Mayo::WidgetMainHome + + + Form + Form + + Mayo::WidgetMeasure Form - Form + Form @@ -3170,42 +3542,42 @@ Lu: %5 Millimeter(mm) - Millimètre(mm) + Millimètre (mm) Centimeter(cm) - Centimètre(cm) + Centimètre (cm) Meter(m) - Mètre(m) + Mètre (m) Inch(in) - Pouce(in) + Pouce (in) Foot(ft) - Pied(ft) + Pied (ft) Yard(yd) - + Yard (yd) Degree(°) - Degré(°) + Degré (°) Radian(rad) - + Radian (rad) @@ -3228,47 +3600,52 @@ Lu: %5 Distance min - + + Center-to-center Distance + Distance entre les centres + + + Length Longueur - + Square Millimeter(mm²) - Millimètre carré(mm²) + Millimètre carré (mm²) - + Square Centimeter(cm²) - Centimètre carré(cm²) + Centimètre carré (cm²) - + Square Meter(m²) - Mètre carré(m²) + Mètre carré (m²) - + Square Inch(in²) - Pouce carré(in²) + Pouce carré (in²) - + Square Foot(ft²) - Pied carré(ft²) + Pied carré (ft²) - + Square Yard(yd²) - Yard carré(yd²) + Yard carré (yd²) - + Angle - + Surface Area Aire surface @@ -3303,7 +3680,7 @@ Lu: %5 Unité angle - + Select entities to measure Sélectionner les entités à mesurer @@ -3343,17 +3720,17 @@ Lu: %5 Montrer %1 - + Instance Instance - + Product Produit - + Both Les Deux @@ -3541,6 +3918,17 @@ Lu: %5 Binaire + + OccStlWriterI18N + + Ascii + Texte + + + Binary + Binaire + + OpenCascade::Aspect_HatchStyle diff --git a/i18n/messages.cpp b/i18n/messages.cpp index 2b6956c2..9770e5ac 100644 --- a/i18n/messages.cpp +++ b/i18n/messages.cpp @@ -2,45 +2,59 @@ namespace { double dummy = 0; } +#include "src/io_gmio/io_gmio_amf_writer.cpp" +#include "src/io_image/io_image.cpp" #include "src/io_occ/io_occ_common.h" #include "src/io_occ/io_occ_stl.cpp" +#include "src/io_occ/io_occ_gltf_writer.cpp" +#include "src/io_ply/io_ply_writer.cpp" #include "src/app/app_module.h" #include "src/app/widget_model_tree_builder_xde.h" -namespace Mayo { static void messages() { - AppModule::textId("VeryCoarse"); - AppModule::textId("Coarse"); - AppModule::textId("Normal"); - AppModule::textId("Precise"); - AppModule::textId("VeryPrecise"); - AppModule::textId("UserDefined"); - - WidgetModelTreeBuilder_Xde::textId("Instance"); - WidgetModelTreeBuilder_Xde::textId("Product"); - WidgetModelTreeBuilder_Xde::textId("Both"); + // App + Mayo::AppModuleProperties::textId("SI"); + Mayo::AppModuleProperties::textId("ImperialUK"); + + Mayo::AppModuleProperties::textId("VeryCoarse"); + Mayo::AppModuleProperties::textId("Coarse"); + Mayo::AppModuleProperties::textId("Normal"); + Mayo::AppModuleProperties::textId("Precise"); + Mayo::AppModuleProperties::textId("VeryPrecise"); + Mayo::AppModuleProperties::textId("UserDefined"); + + Mayo::WidgetModelTreeBuilder_Xde::textId("Instance"); + Mayo::WidgetModelTreeBuilder_Xde::textId("Product"); + Mayo::WidgetModelTreeBuilder_Xde::textId("Both"); + + // I/O + Mayo::IO::GmioAmfWriter::Properties::textId("Decimal"); + Mayo::IO::GmioAmfWriter::Properties::textId("Scientific"); + Mayo::IO::GmioAmfWriter::Properties::textId("Shortest"); + + Mayo::IO::OccCommon::textId("Undefined"); // RWMesh_CoordinateSystem_Undefined + Mayo::IO::OccCommon::textId("posYfwd_posZup"); // RWMesh_CoordinateSystem_Zup + Mayo::IO::OccCommon::textId("negZfwd_posYup"); // RWMesh_CoordinateSystem_Yup + + Mayo::IO::OccCommon::textId("Undefined"); + Mayo::IO::OccCommon::textId("Micrometer"); + Mayo::IO::OccCommon::textId("Millimeter"); + Mayo::IO::OccCommon::textId("Centimeter"); + Mayo::IO::OccCommon::textId("Meter"); + Mayo::IO::OccCommon::textId("Kilometer"); + Mayo::IO::OccCommon::textId("Inch"); + Mayo::IO::OccCommon::textId("Foot"); + Mayo::IO::OccCommon::textId("Mile"); + + Mayo::IO::OccStlWriterI18N::textId("Ascii"); + Mayo::IO::OccStlWriterI18N::textId("Binary"); + + Mayo::IO::OccGltfWriter::Properties::textId("Json"); + Mayo::IO::OccGltfWriter::Properties::textId("Binary"); + + Mayo::IO::PlyWriterI18N::textId("Ascii"); + Mayo::IO::PlyWriterI18N::textId("Binary"); + + Mayo::IO::ImageWriterI18N::textId("Perspective"); + Mayo::IO::ImageWriterI18N::textId("Orthographic"); } - -namespace IO { - -static void messages() { - OccCommon::textId("Undefined"); // RWMesh_CoordinateSystem_Undefined - OccCommon::textId("posYfwd_posZup"); // RWMesh_CoordinateSystem_Zup - OccCommon::textId("negZfwd_posYup"); // RWMesh_CoordinateSystem_Yup - - OccCommon::textId("Undefined"); - OccCommon::textId("Micrometer"); - OccCommon::textId("Millimeter"); - OccCommon::textId("Centimeter"); - OccCommon::textId("Meter"); - OccCommon::textId("Kilometer"); - OccCommon::textId("Inch"); - OccCommon::textId("Foot"); - OccCommon::textId("Mile"); - - OccStlWriter::Properties::textId("Ascii"); - OccStlWriter::Properties::textId("Binary"); -} - -} // namespace IO -} // namespace Mayo diff --git a/images/appicon.icns b/images/appicon.icns new file mode 100644 index 00000000..9a740155 Binary files /dev/null and b/images/appicon.icns differ diff --git a/images/themes/classic/grid.svg b/images/themes/classic/grid.svg new file mode 100644 index 00000000..c09b16a4 --- /dev/null +++ b/images/themes/classic/grid.svg @@ -0,0 +1 @@ +frame-grid \ No newline at end of file diff --git a/images/themes/dark/grid.svg b/images/themes/dark/grid.svg new file mode 100644 index 00000000..c09b16a4 --- /dev/null +++ b/images/themes/dark/grid.svg @@ -0,0 +1 @@ +frame-grid \ No newline at end of file diff --git a/mayo.pro b/mayo.pro index 770c796f..a21b22d9 100644 --- a/mayo.pro +++ b/mayo.pro @@ -14,6 +14,9 @@ CONFIG(debug, debug|release) { } message(Qt version $$QT_VERSION) +!versionAtLeast(QT_VERSION, 5.14) { + error(Qt >= 5.14 is required but detected version is $$QT_VERSION) +} QT += core gui widgets greaterThan(QT_MAJOR_VERSION, 5) { @@ -33,7 +36,7 @@ DEFINES += \ QT_IMPLICIT_QFILEINFO_CONSTRUCTION \ release_with_debuginfo:msvc { - # https://docs.microsoft.com/en-us/cpp/build/reference/how-to-debug-a-release-build + # https://learn.microsoft.com/en-us/cpp/build/how-to-debug-a-release-build?view=msvc-170 QMAKE_CXXFLAGS_RELEASE += /Zi QMAKE_LFLAGS_RELEASE += /DEBUG /INCREMENTAL:NO /OPT:REF /OPT:ICF } @@ -61,6 +64,7 @@ macx { QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 LIBS += -liconv # QMAKE_CXXFLAGS += -mmacosx-version-min=10.15 + ICON = images/appicon.icns } win32 { LIBS += -lOpengl32 -lUser32 @@ -109,11 +113,25 @@ RC_ICONS = images/appicon.ico OTHER_FILES += \ README.md \ - appveyor.yml \ - .github/workflows/ci.yml \ + .github/workflows/ci_linux.yml \ + .github/workflows/ci_macos.yml \ + .github/workflows/ci_windows.yml \ images/credits.txt \ scripts/bump-version.rb \ +# Embed qtbase_*.qm files as a resource +qtBaseQmRes.files = \ + $$[QT_INSTALL_TRANSLATIONS]/qtbase_en.qm \ + $$[QT_INSTALL_TRANSLATIONS]/qtbase_fr.qm +qtBaseQmRes.base = $$[QT_INSTALL_TRANSLATIONS] +qtBaseQmRes.prefix = "/i18n" +RESOURCES += qtBaseQmRes + +# Optional developer-specific QMake pri file for environment related settings +exists($$PWD/env.pri) { + include($$PWD/env.pri) +} + # OpenCascade include(opencascade.pri) !isEmpty(OCC_VERSION_STR) { @@ -155,7 +173,7 @@ LIBS += \ -lTKXmlXCAF \ -lTKXSBase \ -minOpenCascadeVersion(7, 7, 0) { +versionAtLeast(OCC_VERSION_STR, 7.7.0) { LIBS += -lTKXDE } @@ -166,26 +184,62 @@ LIBS += -lTKSTEP -lTKSTEP209 -lTKSTEPAttr -lTKSTEPBase -lTKXDESTEP # -- STL support LIBS += -lTKSTL # -- OBJ/glTF support -minOpenCascadeVersion(7, 4, 0) { +versionAtLeast(OCC_VERSION_STR, 7.4.0) { LIBS += -lTKRWMesh } else { SOURCES -= \ src/io_occ/io_occ_base_mesh.cpp \ src/io_occ/io_occ_gltf_reader.cpp \ src/io_occ/io_occ_obj_reader.cpp + + message(glTF reader disabled because OpenCascade < v7.4) + message(OBJ reader disabled because OpenCascade < v7.4) } -!minOpenCascadeVersion(7, 5, 0) { +!versionAtLeast(OCC_VERSION_STR, 7.5.0) { SOURCES -= src/io_occ/io_occ_gltf_writer.cpp + message(glTF writer disabled because OpenCascade < v7.5) } -!minOpenCascadeVersion(7, 6, 0) { +!versionAtLeast(OCC_VERSION_STR, 7.6.0) { SOURCES -= src/io_occ/io_occ_obj_writer.cpp + message(OBJ writer disabled because OpenCascade < v7.6) } # -- VRML support LIBS += -lTKVRML -!minOpenCascadeVersion(7, 7, 0) { +!versionAtLeast(OCC_VERSION_STR, 7.7.0) { SOURCES -= src/io_occ/io_occ_vrml_reader.cpp + message(VRML reader disabled because OpenCascade < v7.7) +} + +# assimp +isEmpty(ASSIMP_INC_DIR) | isEmpty(ASSIMP_LIB_DIR) { + message(assimp OFF) +} else { + !versionAtLeast(OCC_VERSION_STR, 7.5.0) { + message(assimp reader disabled because OpenCascade < v7.5) + } + else { + message(assimp ON) + ASSIMP_IS_ON = 1 + } +} + +defined(ASSIMP_IS_ON, var) { + HEADERS += $$files(src/io_assimp/*.h) + SOURCES += $$files(src/io_assimp/*.cpp) + + ASSIMP_VERSION_FILE_CONTENTS = $$cat($$ASSIMP_INC_DIR/version.h, lines) + ASSIMP_aiGetVersionPatch = $$find(ASSIMP_VERSION_FILE_CONTENTS, aiGetVersionPatch) + !isEmpty(ASSIMP_aiGetVersionPatch) { + } else { + DEFINES += NO_ASSIMP_aiGetVersionPatch + message(Assimp function aiGetVersionPatch() not available) + } + + INCLUDEPATH += $$ASSIMP_INC_DIR/.. + LIBS += -L$$ASSIMP_LIB_DIR -lassimp$$ASSIMP_LIBNAME_SUFFIX + DEFINES += HAVE_ASSIMP } # gmio @@ -205,10 +259,7 @@ isEmpty(GMIO_ROOT) { INCLUDEPATH += $$GMIO_ROOT/include LIBS += -L$$GMIO_ROOT/lib -lgmio_static -lzlibstatic - SOURCES += \ -# $$GMIO_ROOT/src/gmio_support/stl_occ_brep.cpp \ -# $$GMIO_ROOT/src/gmio_support/stl_occ_polytri.cpp \ - $$GMIO_ROOT/src/gmio_support/stream_qt.cpp + SOURCES += $$GMIO_ROOT/src/gmio_support/stream_qt.cpp DEFINES += HAVE_GMIO } @@ -218,7 +269,7 @@ CONFIG(withtests) { DEFINES += MAYO_WITH_TESTS } -# Developer custom processing +# Optional developer-specific QMake pri file for custom processing exists($$PWD/custom.pri) { include($$PWD/custom.pri) } diff --git a/mayo.qrc b/mayo.qrc index aecced61..b446ce4f 100644 --- a/mayo.qrc +++ b/mayo.qrc @@ -82,5 +82,7 @@ images/themes/classic/turn-cw.svg images/themes/dark/turn-ccw.svg images/themes/dark/turn-cw.svg + images/themes/classic/grid.svg + images/themes/dark/grid.svg diff --git a/opencascade.pri b/opencascade.pri index 76544374..18feb540 100644 --- a/opencascade.pri +++ b/opencascade.pri @@ -47,35 +47,8 @@ equals(QT_ARCH, i386) { } else:equals(QT_ARCH, x86_64) { DEFINES += _OCC64 } else { - warning(Platform architecture may be not supported (QT_ARCH = $$QT_ARCH)) + warning(Platform architecture may be not supported(QT_ARCH = $$QT_ARCH)) } LIBS += $$system_path($$join(CASCADE_LIB_DIR, " -L", -L)) QMAKE_RPATHDIR += $$CASCADE_LIB_DIR - -defineTest(minOpenCascadeVersion) { - maj = $$1 - min = $$2 - patch = $$3 - isEqual(OCC_VERSION_MAJOR, $$maj) { - isEqual(OCC_VERSION_MINOR, $$min) { - isEqual(OCC_VERSION_PATCH, $$patch) { - return(true) - } - - greaterThan(OCC_VERSION_PATCH, $$patch) { - return(true) - } - } - - greaterThan(OCC_VERSION_MINOR, $$min) { - return(true) - } - } - - greaterThan(OCC_VERSION_MAJOR, $$maj) { - return(true) - } - - return(false) -} diff --git a/scripts/bump-version.rb b/scripts/bump-version.rb index 13a54c90..dadfb5e5 100644 --- a/scripts/bump-version.rb +++ b/scripts/bump-version.rb @@ -33,10 +33,3 @@ version_pri.sub!(/(MAYO_VERSION_PAT\s*=\s*)\d+/, "\\1#{patch}") File.open(path_version_pri, "w").write(version_pri) puts "Bumped #{path_version_pri}" - -# ../appveyor.yml -path_appveyor_yml = "#{script_dir_name}/../appveyor.yml" -appveyor_yml = File.open(path_appveyor_yml, "r").read -appveyor_yml.sub!(/(version\s*:\s*)\d+\.\d+/, "\\1#{major}.#{minor}") -File.open(path_appveyor_yml, "w").write(appveyor_yml) -puts "Bumped #{path_appveyor_yml}" diff --git a/src/app/app_context.cpp b/src/app/app_context.cpp index a4778116..8cecc24e 100644 --- a/src/app/app_context.cpp +++ b/src/app/app_context.cpp @@ -9,16 +9,24 @@ #include "mainwindow.h" #include "ui_mainwindow.h" #include "widget_gui_document.h" +#include "widget_main_control.h" +#include "widget_main_home.h" + +#include namespace Mayo { AppContext::AppContext(MainWindow* wnd) - : m_wnd(wnd) + : IAppContext(wnd), + m_wnd(wnd) { + assert(m_wnd != nullptr); + assert(m_wnd->widgetPageDocuments() != nullptr); + QObject::connect( - m_wnd->m_ui->combo_GuiDocuments, qOverload(&QComboBox::currentIndexChanged), + m_wnd->widgetPageDocuments(), &WidgetMainControl::currentDocumentIndexChanged, this, &AppContext::onCurrentDocumentIndexChanged - ); + ); } GuiApplication* AppContext::guiApp() const @@ -31,25 +39,43 @@ TaskManager* AppContext::taskMgr() const return &m_wnd->m_taskMgr; } +QWidget* AppContext::pageDocuments_widgetLeftSideBar() const +{ + const WidgetMainControl* pageDocs = m_wnd->widgetPageDocuments(); + return pageDocs ? pageDocs->widgetLeftSideBar() : nullptr; +} + QWidget* AppContext::widgetMain() const { return m_wnd; } -QWidget* AppContext::widgetLeftSidebar() const +QWidget* AppContext::widgetPage(Page page) const { - return m_wnd->m_ui->widget_Left; + if (page == Page::Home) + return m_wnd->widgetPageHome(); + else if (page == Page::Documents) + return m_wnd->widgetPageDocuments(); + else + return nullptr; } -IAppContext::ModeWidgetMain AppContext::modeWidgetMain() const +IAppContext::Page AppContext::currentPage() const { auto widget = m_wnd->m_ui->stack_Main->currentWidget(); - if (widget == m_wnd->m_ui->page_MainHome) - return ModeWidgetMain::Home; - else if (widget == m_wnd->m_ui->page_MainControl) - return ModeWidgetMain::Documents; + if (widget == m_wnd->widgetPageHome()) + return Page::Home; + else if (widget == m_wnd->widgetPageDocuments()) + return Page::Documents; + + return Page::Unknown; +} - return ModeWidgetMain::Unknown; +void AppContext::setCurrentPage(Page page) +{ + QWidget* widgetPage = this->widgetPage(page); + assert(widgetPage); + m_wnd->m_ui->stack_Main->setCurrentWidget(widgetPage); } V3dViewController* AppContext::v3dViewController(const GuiDocument* guiDoc) const @@ -72,14 +98,14 @@ int AppContext::findDocumentIndex(Document::Identifier docId) const Document::Identifier AppContext::findDocumentFromIndex(int index) const { - auto widgetDoc = m_wnd->widgetGuiDocument(index); + auto widgetDoc = this->widgetGuiDocument(index); return widgetDoc ? widgetDoc->documentIdentifier() : -1; } Document::Identifier AppContext::currentDocument() const { - const int index = m_wnd->m_ui->combo_GuiDocuments->currentIndex(); - auto widgetDoc = m_wnd->widgetGuiDocument(index); + const int index = m_wnd->widgetPageDocuments()->currentDocumentIndex(); + auto widgetDoc = this->widgetGuiDocument(index); return widgetDoc ? widgetDoc->documentIdentifier() : -1; } @@ -88,8 +114,8 @@ void AppContext::setCurrentDocument(Document::Identifier docId) auto widgetDoc = this->findWidgetGuiDocument([=](WidgetGuiDocument* widgetDoc) { return widgetDoc->documentIdentifier() == docId; }); - const int docIndex = m_wnd->m_ui->stack_GuiDocuments->indexOf(widgetDoc); - m_wnd->m_ui->combo_GuiDocuments->setCurrentIndex(docIndex); + const int docIndex = m_wnd->widgetPageDocuments()->indexOfWidgetGuiDocument(widgetDoc); + m_wnd->widgetPageDocuments()->setCurrentDocumentIndex(docIndex); } void AppContext::updateControlsEnabledStatus() @@ -99,20 +125,22 @@ void AppContext::updateControlsEnabledStatus() void AppContext::deleteDocumentWidget(const DocumentPtr& doc) { - QWidget* widgetDoc = this->findWidgetGuiDocument([&](WidgetGuiDocument* widgetDoc) { + auto widgetDoc = this->findWidgetGuiDocument([&](WidgetGuiDocument* widgetDoc) { return widgetDoc->documentIdentifier() == doc->identifier(); }); - if (widgetDoc) { - m_wnd->m_ui->stack_GuiDocuments->removeWidget(widgetDoc); - widgetDoc->deleteLater(); - } + m_wnd->widgetPageDocuments()->removeWidgetGuiDocument(widgetDoc); +} + +WidgetGuiDocument* AppContext::widgetGuiDocument(int idx) const +{ + return m_wnd->widgetPageDocuments()->widgetGuiDocument(idx); } WidgetGuiDocument* AppContext::findWidgetGuiDocument(std::function fn) const { - const int widgetCount = m_wnd->m_ui->stack_GuiDocuments->count(); + const int widgetCount = m_wnd->widgetPageDocuments()->widgetGuiDocumentCount(); for (int i = 0; i < widgetCount; ++i) { - auto candidate = m_wnd->widgetGuiDocument(i); + auto candidate = this->widgetGuiDocument(i); if (candidate && fn(candidate)) return candidate; } @@ -122,7 +150,7 @@ WidgetGuiDocument* AppContext::findWidgetGuiDocument(std::functionwidgetGuiDocument(docIndex); + auto widgetDoc = this->widgetGuiDocument(docIndex); emit this->currentDocumentChanged(widgetDoc ? widgetDoc->documentIdentifier() : -1); } diff --git a/src/app/app_context.h b/src/app/app_context.h index 583ef5e4..942011bd 100644 --- a/src/app/app_context.h +++ b/src/app/app_context.h @@ -20,11 +20,14 @@ class AppContext : public IAppContext { GuiApplication* guiApp() const override; TaskManager* taskMgr() const override; - QWidget* widgetMain() const override; - QWidget* widgetLeftSidebar() const override; - ModeWidgetMain modeWidgetMain() const override; V3dViewController* v3dViewController(const GuiDocument* guiDoc) const override; + QWidget* widgetMain() const override; + QWidget* widgetPage(Page page) const override; + Page currentPage() const override; + void setCurrentPage(Page page) override; + QWidget* pageDocuments_widgetLeftSideBar() const override; + int findDocumentIndex(Document::Identifier docId) const override; Document::Identifier findDocumentFromIndex(int index) const override; @@ -35,6 +38,7 @@ class AppContext : public IAppContext { void deleteDocumentWidget(const DocumentPtr& doc) override; private: + WidgetGuiDocument* widgetGuiDocument(int idx) const; WidgetGuiDocument* findWidgetGuiDocument(std::function fn) const; void onCurrentDocumentIndexChanged(int docIndex); diff --git a/src/app/app_module.cpp b/src/app/app_module.cpp index 46e87e49..efa02e97 100644 --- a/src/app/app_module.cpp +++ b/src/app/app_module.cpp @@ -38,7 +38,7 @@ AppModule::AppModule() { static bool metaTypesRegistered = false; if (!metaTypesRegistered) { - qRegisterMetaType("Messenger::MessageType"); + qRegisterMetaType("MessageType"); metaTypesRegistered = true; } @@ -139,7 +139,7 @@ bool AppModule::fromVariant(Property* prop, const Settings::Variant& variant) co } } -void AppModule::emitMessage(Messenger::MessageType msgType, std::string_view text) +void AppModule::emitMessage(MessageType msgType, std::string_view text) { const QString qtext = to_QString(text); { diff --git a/src/app/app_module.h b/src/app/app_module.h index 5fadb7a7..42e02b67 100644 --- a/src/app/app_module.h +++ b/src/app/app_module.h @@ -74,7 +74,7 @@ class AppModule : // Logging void clearMessageLog(); Span messageLog() const { return m_messageLog; } - Signal signalMessage; + Signal signalMessage; Signal<> signalMessageLogCleared; // Recent files diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp index a66b597f..e513e7ed 100644 --- a/src/app/app_module_properties.cpp +++ b/src/app/app_module_properties.cpp @@ -155,6 +155,9 @@ void AppModuleProperties::IO_bindParameters(const IO::System* ioSystem) void AppModuleProperties::retranslate() { + // System + this->unitSystemSchema.mutableEnumeration().changeTrContext(AppModuleProperties::textIdContext()); + // Application this->language.setDescription( textIdTr("Language used for the application. Change will take effect after application restart")); diff --git a/src/app/command_system_information.cpp b/src/app/command_system_information.cpp new file mode 100644 index 00000000..5d149644 --- /dev/null +++ b/src/app/command_system_information.cpp @@ -0,0 +1,470 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "commands_help.h" + +#include "app_module.h" +#include "qstring_conv.h" +#include "qtwidgets_utils.h" +#include "version.h" +#include "../base/meta_enum.h" +#include "../base/filepath.h" +#include "../base/io_system.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// NOTICE for Linux/X11 +// Because of #define conflicts, OpenGL_Context.hxx must be included *before* QtGui/QOpenGLContext +// It also has to be included *after* QtCore/QTextStream +// Beware of these limitations when adding/removing inclusion of headers here +#include +#include + +#include +#include + +#ifdef HAVE_GMIO +# include +#endif + +#include +#include + +namespace Mayo { + +CommandSystemInformation::CommandSystemInformation(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("System Information...")); + this->setAction(action); +} + +void CommandSystemInformation::execute() +{ + const QString text = CommandSystemInformation::data(); + + auto dlg = new QDialog(this->widgetMain()); + dlg->setWindowTitle(this->action()->text()); + dlg->resize(800 * dlg->devicePixelRatioF(), 600 * dlg->devicePixelRatioF()); + + auto textEdit = new QPlainTextEdit(dlg); + textEdit->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + textEdit->setReadOnly(true); + textEdit->setPlainText(text); + + auto btnBox = new QDialogButtonBox(dlg); + btnBox->addButton(QDialogButtonBox::Close); + auto btnCopy = btnBox->addButton(Command::tr("Copy to Clipboard"), QDialogButtonBox::ActionRole); + QObject::connect(btnCopy, &QAbstractButton::clicked, this, [=]{ + QGuiApplication::clipboard()->setText(text); + }); + QObject::connect(btnBox, &QDialogButtonBox::rejected, dlg, &QDialog::reject); + + auto layout = new QVBoxLayout(dlg); + layout->addWidget(textEdit); + layout->addWidget(btnBox); + + QtWidgetsUtils::asyncDialogExec(dlg); +} + +namespace { + +QTextStream& operator<<(QTextStream& str, bool v) +{ + str << (v ? "true" : "false"); + return str; +} + +QTextStream& operator<<(QTextStream& str, const QFont& f) +{ + str << '"' << f.family() << "\" " << f.pointSize(); + return str; +} + +QTextStream& operator<<(QTextStream& str, const QSize& sz) +{ + str << sz.width() << 'x' << sz.height(); + return str; +} + +QTextStream& operator<<(QTextStream& str, const QSizeF& sz) +{ + str << sz.width() << 'x' << sz.height(); + return str; +} + +QTextStream& operator<<(QTextStream& str, const QRect& r) +{ + str << '(' << r.x() << ", " << r.y() << ", " << r.size() << ")"; + return str; +} + +QTextStream& operator<<(QTextStream& str, std::string_view sv) +{ + str << to_QString(sv); + return str; +} + +const char indent[] = " "; +const char indentx2[] = " "; + +std::vector& getLibraryInfos() +{ + static std::vector vec; + return vec; +} + +// Helper function returning unicode representation of a QChar object in the form "U+NNNN" +QString toUnicodeHexa(const QChar& ch) +{ + QString strHexa; + QTextStream ostr(&strHexa); + ostr.setNumberFlags(ostr.numberFlags() | QTextStream::UppercaseDigits); + ostr.setIntegerBase(16); // Same as Qt::hex + ostr << "U+" << qSetFieldWidth(4) << qSetPadChar('0') << static_cast(ch.unicode()); + return strHexa; +} + +// Helper function returning unicode representation of a QString object in the form "U+NNNNU+PPPP..." +[[maybe_unused]] QString toUnicodeHexa(const QString& str) +{ + QString strHexa; + QTextStream ostr(&strHexa); + ostr.setNumberFlags(ostr.numberFlags() | QTextStream::UppercaseDigits); + ostr.setIntegerBase(16); // Same as Qt::hex + for (const QChar& ch : str) + ostr << "U+" << qSetFieldWidth(4) << qSetPadChar('0') << static_cast(ch.unicode()); + + return strHexa; +} + +} // namespace + +static void dumpOpenGlInfo(QTextStream& str) +{ + QOpenGLContext qtContext; + if (!qtContext.create()) { + str << "Unable to create QOpenGLContext object" << '\n'; + return; + } + + QWindow window; + window.setSurfaceType(QSurface::OpenGLSurface); + window.create(); + qtContext.makeCurrent(&window); + + OpenGl_Context occContext; + if (!occContext.Init()) { + str << "Unable to initialize OpenGl_Context object" << '\n'; + return; + } + + TColStd_IndexedDataMapOfStringString dict; + occContext.DiagnosticInformation(dict, Graphic3d_DiagnosticInfo_Basic); + for (TColStd_IndexedDataMapOfStringString::Iterator it(dict); it.More(); it.Next()) + str << indent << to_QString(it.Key()) << ": " << to_QString(it.Value()) << '\n'; + + str << indent << "MaxDegreeOfAnisotropy: " << occContext.MaxDegreeOfAnisotropy() << '\n' + << indent << "MaxDrawBuffers: " << occContext.MaxDrawBuffers() << '\n' + << indent << "MaxClipPlanes: " << occContext.MaxClipPlanes() << '\n' + << indent << "HasRayTracing: " << occContext.HasRayTracing() << '\n' + << indent << "HasRayTracingTextures: " << occContext.HasRayTracingTextures() << '\n' + << indent << "HasRayTracingAdaptiveSampling: " << occContext.HasRayTracingAdaptiveSampling() << '\n' + << indent << "UseVBO: " << occContext.ToUseVbo() << '\n'; + +#if OCC_VERSION_HEX >= 0x070400 + str << indent << "MaxDumpSizeX: " << occContext.MaxDumpSizeX() << '\n' + << indent << "MaxDumpSizeY: " << occContext.MaxDumpSizeY() << '\n' + << indent << "HasRayTracingAdaptiveSamplingAtomic: " << occContext.HasRayTracingAdaptiveSamplingAtomic() << '\n'; +#endif + +#if OCC_VERSION_HEX >= 0x070500 + str << indent << "HasTextureBaseLevel: " << occContext.HasTextureBaseLevel() << '\n' + << indent << "HasSRGB: " << occContext.HasSRGB() << '\n' + << indent << "RenderSRGB: " << occContext.ToRenderSRGB() << '\n' + << indent << "IsWindowSRGB: " << occContext.IsWindowSRGB() << '\n' + << indent << "HasPBR: " << occContext.HasPBR() << '\n'; +#endif + +#if OCC_VERSION_HEX >= 0x070700 + str << indent << "GraphicsLibrary: " << MetaEnum::name(occContext.GraphicsLibrary()) << '\n' + << indent << "HasTextureMultisampling: " << occContext.HasTextureMultisampling() << '\n'; +#endif +} + +QString CommandSystemInformation::data() +{ + QString strSysInfo; + QTextStream ostr(&strSysInfo); + + // Mayo version + ostr << '\n' + << "Mayo: v" << strVersion + << " commit:" << strVersionCommitId + << " revnum:" << versionRevisionNumber + << " " << QT_POINTER_SIZE * 8 << "bit" + << '\n'; + + // OS version + ostr << '\n' << "OS: " << QSysInfo::prettyProductName() + << " [" << QSysInfo::kernelType() << " version " << QSysInfo::kernelVersion() << "]" << '\n' + << "Current CPU Architecture: " << QSysInfo::currentCpuArchitecture() << '\n'; + + // Qt version + ostr << '\n' << QLibraryInfo::build() << " on \"" << QGuiApplication::platformName() << "\" " << '\n' + << indent << "QStyle keys: " << QStyleFactory::keys().join("; ") << '\n' + << indent << "Image formats(read): " << QImageReader::supportedImageFormats().join(' ') << '\n' + << indent << "Image formats(write): " << QImageWriter::supportedImageFormats().join(' ') << '\n'; + + // OpenCascade version + ostr << '\n' << "OpenCascade: " << OCC_VERSION_STRING_EXT << " (build)" << '\n'; + + // Other registered libraries + for (const LibraryInfo& libInfo : getLibraryInfos()) { + ostr << '\n' << to_QString(libInfo.name) << ": " << to_QString(libInfo.version) + << " " << to_QString(libInfo.versionDetails) + << '\n'; + } + + // I/O supported formats + { + ostr << '\n' << "Import(read) formats:" << '\n' << indent; + const IO::System* ioSystem = AppModule::get()->ioSystem(); + for (IO::Format format : ioSystem->readerFormats()) + ostr << IO::formatIdentifier(format) << " "; + + ostr << '\n' << "Export(write) formats:" << '\n' << indent; + for (IO::Format format : ioSystem->writerFormats()) + ostr << IO::formatIdentifier(format) << " "; + + ostr << '\n'; + } + + // Locales + ostr << '\n' << "Locale:\n"; + { + const std::locale& stdLoc = AppModule::get()->stdLocale(); + const auto& numFacet = std::use_facet>(stdLoc); + const QChar charDecPnt(numFacet.decimal_point()); + const QChar char1000Sep(numFacet.thousands_sep()); + std::string strGrouping; + for (char c : numFacet.grouping()) + strGrouping += std::to_string(static_cast(c)) + " "; + + ostr << indent << "std::locale:" << '\n' + << indentx2 << "name: " << to_QString(stdLoc.name()) << '\n' + << indentx2 << "numpunct.decimal_point: " << charDecPnt << " " << toUnicodeHexa(charDecPnt) << '\n' + << indentx2 << "numpunct.thousands_sep: " << char1000Sep << " " << toUnicodeHexa(char1000Sep) << '\n' + << indentx2 << "numpunct.grouping: " << to_QString(strGrouping) << '\n' + << indentx2 << "numpunct.truename: " << to_QString(numFacet.truename()) << '\n' + << indentx2 << "numpunct.falsename: " << to_QString(numFacet.falsename()) << '\n'; + const QLocale& qtLoc = AppModule::get()->qtLocale(); + ostr << indent << "QLocale:" << '\n' + << indentx2 << "name: " << qtLoc.name() << '\n' + << indentx2 << "language: " << MetaEnum::name(qtLoc.language()) << '\n' + << indentx2 << "measurementSytem: " << MetaEnum::name(qtLoc.measurementSystem()) << '\n' + << indentx2 << "textDirection: " << MetaEnum::name(qtLoc.textDirection()) << '\n' + << indentx2 << "decimalPoint: " << qtLoc.decimalPoint() << " " << toUnicodeHexa(qtLoc.decimalPoint()) << '\n' + << indentx2 << "groupSeparator: " << qtLoc.groupSeparator() << " " << toUnicodeHexa(qtLoc.groupSeparator()) << '\n'; + } + + // C++ StdLib + ostr << '\n' << "C++ StdLib:\n"; + { + const QChar dirSeparator(FilePath::preferred_separator); + ostr << indent << "std::thread::hardware_concurrency: " << std::thread::hardware_concurrency() << '\n' + << indent << "std::filepath::path::preferred_separator: " << dirSeparator << " " << toUnicodeHexa(dirSeparator) << '\n'; + } + + // OpenGL + ostr << '\n' << "OpenGL:" << '\n'; + dumpOpenGlInfo(ostr); + + // File selectors + ostr << '\n' << "File selectors(increasing order of precedence):\n" << indent; + for (const QString& selector : QFileSelector().allSelectors()) + ostr << selector << " "; + + ostr << '\n'; + + // Fonts + ostr << '\n' << "Fonts:\n"; + ostr << indent << "General font: " << QFontDatabase::systemFont(QFontDatabase::GeneralFont) << '\n' + << indent << "Fixed font: " << QFontDatabase::systemFont(QFontDatabase::FixedFont) << '\n' + << indent << "Title font: " << QFontDatabase::systemFont(QFontDatabase::TitleFont) << '\n' + << indent << "Smallest font: " << QFontDatabase::systemFont(QFontDatabase::SmallestReadableFont) << '\n'; + + // Screens + ostr << '\n' << "Screens:\n"; + { + const QList listScreen = QGuiApplication::screens(); + ostr << indent << "Count: " << listScreen.size() << '\n'; + for (int i = 0; i < listScreen.size(); ++i) { + const QScreen* screen = listScreen.at(i); + ostr << indent << "Screen #" << i << '\n'; + ostr << indentx2 << "name: " << screen->name() << '\n' + << indentx2 << "model: " << screen->model() << '\n' + << indentx2 << "manufacturer: " << screen->manufacturer() << '\n' + << indentx2 << "serialNumber: " << screen->serialNumber() << '\n' + << indentx2 << "depth: " << screen->depth() << '\n' + << indentx2 << "size: " << screen->size() << '\n' + << indentx2 << "availableSize: " << screen->availableSize() << '\n' + << indentx2 << "virtualSize: " << screen->virtualSize() << '\n' + << indentx2 << "availableVirtualSize: " << screen->availableVirtualSize() << '\n' + << indentx2 << "geometry: " << screen->geometry() << '\n' + << indentx2 << "availableGeometry: " << screen->availableGeometry() << '\n' + << indentx2 << "virtualGeometry: " << screen->virtualGeometry() << '\n' + << indentx2 << "availableVirtualGeometry: " << screen->availableVirtualGeometry() << '\n' + << indentx2 << "physicalSize: " << screen->physicalSize() << '\n' + << indentx2 << "physicalDotsPerInchX: " << screen->physicalDotsPerInchX() << '\n' + << indentx2 << "physicalDotsPerInchY: " << screen->physicalDotsPerInchY() << '\n' + << indentx2 << "physicalDotsPerInch: " << screen->physicalDotsPerInch() << '\n' + << indentx2 << "logicalDotsPerInchX: " << screen->logicalDotsPerInchX() << '\n' + << indentx2 << "logicalDotsPerInchY: " << screen->logicalDotsPerInchY() << '\n' + << indentx2 << "logicalDotsPerInch: " << screen->logicalDotsPerInch() << '\n' + << indentx2 << "devicePixelRatio: " << screen->devicePixelRatio() << '\n' + << indentx2 << "logicalDotsPerInch: " << screen->logicalDotsPerInch() << '\n' + << indentx2 << "primaryOrientation: " << MetaEnum::name(screen->primaryOrientation()) << '\n' + << indentx2 << "orientation: " << MetaEnum::name(screen->orientation()) << '\n' + << indentx2 << "nativeOrientation: " << MetaEnum::name(screen->nativeOrientation()) << '\n' + << indentx2 << "refreshRate: " << screen->refreshRate() << "Hz" << '\n'; + } + } + + // QGuiApplication + ostr << '\n' << "QGuiApplication:\n"; + ostr << indent << "platformName: " << QGuiApplication::platformName() << '\n' + << indent << "desktopFileName: " << QGuiApplication::desktopFileName() << '\n' + << indent << "desktopSettingsAware: " << QGuiApplication::desktopSettingsAware() << '\n' + << indent << "layoutDirection: " << MetaEnum::name(QGuiApplication::layoutDirection()) << '\n'; + const QStyleHints* sh = QGuiApplication::styleHints(); + if (sh) { + const auto pwdChar = sh->passwordMaskCharacter(); + ostr << indent << "styleHints:\n" + << indentx2 << "keyboardAutoRepeatRate: " << sh->keyboardAutoRepeatRate() << '\n' + << indentx2 << "keyboardInputInterval: " << sh->keyboardInputInterval() << '\n' + << indentx2 << "mouseDoubleClickInterval: " << sh->mouseDoubleClickInterval() << '\n' + << indentx2 << "mousePressAndHoldInterval: " << sh->mousePressAndHoldInterval() << '\n' + << indentx2 << "passwordMaskCharacter: " << pwdChar << " " << toUnicodeHexa(pwdChar) << '\n' + << indentx2 << "passwordMaskDelay: " << sh->passwordMaskDelay() << '\n' + << indentx2 << "setFocusOnTouchRelease: " << sh->setFocusOnTouchRelease() << '\n' + << indentx2 << "showIsFullScreen: " << sh->showIsFullScreen() << '\n' + << indentx2 << "showIsMaximized: " << sh->showIsMaximized() << '\n' + << indentx2 << "showShortcutsInContextMenus: " << sh->showShortcutsInContextMenus() << '\n' + << indentx2 << "startDragDistance: " << sh->startDragDistance() << '\n' + << indentx2 << "startDragTime: " << sh->startDragTime() << '\n' + << indentx2 << "startDragVelocity: " << sh->startDragVelocity() << '\n' + << indentx2 << "useRtlExtensions: " << sh->useRtlExtensions() << '\n' + << indentx2 << "tabFocusBehavior: " << MetaEnum::name(sh->tabFocusBehavior()) << '\n' + << indentx2 << "singleClickActivation: " << sh->singleClickActivation() << '\n' + << indentx2 << "useHoverEffects: " << sh->useHoverEffects() << '\n' + << indentx2 << "wheelScrollLines: " << sh->wheelScrollLines() << '\n' + << indentx2 << "mouseQuickSelectionThreshold: " << sh->mouseQuickSelectionThreshold() << '\n' +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + << indentx2 << "mouseDoubleClickDistance: " << sh->mouseDoubleClickDistance() << '\n' + << indentx2 << "touchDoubleTapDistance: " << sh->touchDoubleTapDistance() << '\n' +#endif + ; + } + + // Library info + ostr << '\n' << "Library info:\n"; + { + auto fnLibInfo = [&](QLibraryInfo::LibraryLocation loc) { + const QString locDir = QDir::toNativeSeparators(QLibraryInfo::QLibraryInfo::location(loc)); + ostr << indent << MetaEnum::name(loc) << ": " << locDir << '\n'; + }; + fnLibInfo(QLibraryInfo::PrefixPath); + fnLibInfo(QLibraryInfo::HeadersPath); + fnLibInfo(QLibraryInfo::LibrariesPath); + fnLibInfo(QLibraryInfo::LibraryExecutablesPath); + fnLibInfo(QLibraryInfo::BinariesPath); + fnLibInfo(QLibraryInfo::PluginsPath); + fnLibInfo(QLibraryInfo::ArchDataPath); + fnLibInfo(QLibraryInfo::DataPath); + fnLibInfo(QLibraryInfo::TranslationsPath); + fnLibInfo(QLibraryInfo::SettingsPath); + } + + // Standard paths + ostr << '\n' << "Standard Paths:\n"; + { + auto fnStdPath = [&](QStandardPaths::StandardLocation loc) { + QStringList locValues = QStandardPaths::standardLocations(loc); + for (QString& dir : locValues) + dir = QDir::toNativeSeparators(dir); + + ostr << indent << MetaEnum::name(loc) << ": " << locValues.join("; ") << '\n'; + }; + fnStdPath(QStandardPaths::DocumentsLocation); + fnStdPath(QStandardPaths::FontsLocation); + fnStdPath(QStandardPaths::ApplicationsLocation); + fnStdPath(QStandardPaths::TempLocation); + fnStdPath(QStandardPaths::HomeLocation); + fnStdPath(QStandardPaths::CacheLocation); + fnStdPath(QStandardPaths::GenericCacheLocation); + fnStdPath(QStandardPaths::GenericDataLocation); + fnStdPath(QStandardPaths::RuntimeLocation); + fnStdPath(QStandardPaths::ConfigLocation); + fnStdPath(QStandardPaths::GenericConfigLocation); + fnStdPath(QStandardPaths::AppDataLocation); + fnStdPath(QStandardPaths::AppLocalDataLocation); + fnStdPath(QStandardPaths::AppConfigLocation); + } + + // Environment + ostr << '\n' << "Environment:\n"; + { + const QProcessEnvironment sysEnv = QProcessEnvironment::systemEnvironment(); + for (const QString& key : sysEnv.keys()) + ostr << indent << key << "=\"" << sysEnv.value(key) << "\"\n"; + } + + ostr.flush(); + return strSysInfo; +} + +void CommandSystemInformation::addLibraryInfo( + std::string_view libName, + std::string_view version, + std::string_view versionDetails + ) +{ + if (!libName.empty() && !version.empty()) { + const LibraryInfo libInfo{ + std::string(libName), + std::string(version), + std::string(versionDetails) + }; + getLibraryInfos().push_back(std::move(libInfo)); + } +} + +Span CommandSystemInformation::libraryInfos() +{ + return getLibraryInfos(); +} + +} // namespace Mayo diff --git a/src/app/commands_api.cpp b/src/app/commands_api.cpp index 5ed5b40f..d7c1a500 100644 --- a/src/app/commands_api.cpp +++ b/src/app/commands_api.cpp @@ -10,6 +10,8 @@ #include "../gui/gui_application.h" #include +#include +#include namespace Mayo { @@ -26,7 +28,7 @@ Command::Command(IAppContext* context) Application* Command::app() const { - return m_context->guiApp()->application().get(); + return m_context ? m_context->guiApp()->application().get() : nullptr; } GuiDocument* Command::currentGuiDocument() const @@ -37,7 +39,7 @@ GuiDocument* Command::currentGuiDocument() const int Command::currentDocumentIndex() const { - return this->context()->findDocumentIndex(this->currentDocument()); + return m_context ? m_context->findDocumentIndex(this->currentDocument()) : -1; } void Command::setCurrentDocument(const DocumentPtr& doc) @@ -51,4 +53,44 @@ void Command::setAction(QAction* action) QObject::connect(action, &QAction::triggered, this, &Command::execute); } +CommandContainer::CommandContainer(IAppContext* appContext) + : m_appContext(appContext) +{ +} + +void CommandContainer::setAppContext(IAppContext* appContext) +{ + assert(!m_appContext && m_mapCommand.empty()); + m_appContext = appContext; +} + +Command* CommandContainer::findCommand(std::string_view name) const +{ + auto it = m_mapCommand.find(name); + return it != m_mapCommand.cend() ? it->second : nullptr; +} + +QAction* CommandContainer::findCommandAction(std::string_view name) const +{ + auto cmd = this->findCommand(name); + return cmd ? cmd->action() : nullptr; +} + +void CommandContainer::clear() +{ + for (auto [name, cmd] : m_mapCommand) { + delete cmd; + } + + m_mapCommand.clear(); +} + +void CommandContainer::addCommand_impl(std::string_view name, Command* cmd) +{ + assert(m_appContext != nullptr); + auto [it, ok] = m_mapCommand.insert({ name, cmd }); + if (!ok) + throw std::invalid_argument(fmt::format("Command name {} already exists", name)); +} + } // namespace Mayo diff --git a/src/app/commands_api.h b/src/app/commands_api.h index 755ce7e1..1b70fdcd 100644 --- a/src/app/commands_api.h +++ b/src/app/commands_api.h @@ -12,6 +12,7 @@ #include #include // WARNING Qt5 / Qt6 +#include class QWidget; namespace Mayo { @@ -22,21 +23,26 @@ class GuiDocument; class V3dViewController; class TaskManager; +class Command; + // Provides interface to access/interact with application class IAppContext : public QObject { Q_OBJECT public: - enum class ModeWidgetMain { Unknown, Home, Documents }; + enum class Page { Unknown, Home, Documents }; IAppContext(QObject* parent = nullptr); virtual GuiApplication* guiApp() const = 0; virtual TaskManager* taskMgr() const = 0; - virtual QWidget* widgetMain() const = 0; - virtual QWidget* widgetLeftSidebar() const = 0; - virtual ModeWidgetMain modeWidgetMain() const = 0; virtual V3dViewController* v3dViewController(const GuiDocument* guiDoc) const = 0; + virtual QWidget* widgetMain() const = 0; + virtual QWidget* widgetPage(Page page) const = 0; + virtual Page currentPage() const = 0; + virtual void setCurrentPage(Page page) = 0; + virtual QWidget* pageDocuments_widgetLeftSideBar() const = 0; + virtual Document::Identifier currentDocument() const = 0; virtual void setCurrentDocument(Document::Identifier docId) = 0; @@ -67,10 +73,10 @@ class Command : public QObject { protected: Application* app() const; - GuiApplication* guiApp() const { return m_context->guiApp(); } - TaskManager* taskMgr() const { return m_context->taskMgr(); } - QWidget* widgetMain() const { return m_context->widgetMain(); } - Document::Identifier currentDocument() const { return m_context->currentDocument(); } + GuiApplication* guiApp() const { return m_context ? m_context->guiApp() : nullptr; } + TaskManager* taskMgr() const { return m_context ? m_context->taskMgr() : nullptr; } + QWidget* widgetMain() const { return m_context ? m_context->widgetMain() : nullptr; } + Document::Identifier currentDocument() const { return m_context ? m_context->currentDocument() : -1; } GuiDocument* currentGuiDocument() const; int currentDocumentIndex() const; @@ -82,4 +88,72 @@ class Command : public QObject { QAction* m_action = nullptr; }; +// Provides an associative container dedicated to Command objects +// Each command in the container is mapped to a unique identifier(ie the "command name") +class CommandContainer { +public: + CommandContainer() = default; + CommandContainer(IAppContext* appContext); + + IAppContext* appContext() const { return m_appContext; } + void setAppContext(IAppContext* appContext); + + template void foreachCommand(Function fn); + + // Returns the Command object mapped to 'name' + // That object was previously created and associated with a call to addCommand()/addNamedCommand() + // Might return null in case no command is mapped to 'name' + Command* findCommand(std::string_view name) const; + + // Helper function to retrieve the action provided by the Command object mapped to 'name' + // Might return null in case no command is mapped to 'name' + QAction* findCommandAction(std::string_view name) const; + + // Construct and add new Command object with arguments 'args' + // The command is associated to identigfier 'name' and can be retrieved later on with findCommand() + template CmdType* addCommand(std::string_view name, Args... p); + + // Same behavior as addCommand() function + // The command name is implicit and found by assuming the presence of CmdType::Name class member + template CmdType* addNamedCommand(Args... p); + + void clear(); + +private: + void addCommand_impl(std::string_view name, Command* cmd); + + IAppContext* m_appContext = nullptr; + std::unordered_map m_mapCommand; +}; + + + +// -- +// -- Implementation +// -- + +template +void CommandContainer::foreachCommand(Function fn) +{ + for (auto [name, cmd] : m_mapCommand) { + fn(name, cmd); + } +} + +template +CmdType* CommandContainer::addCommand(std::string_view name, Args... p) +{ + auto cmd = new CmdType(m_appContext, p...); + this->addCommand_impl(name, cmd); + return cmd; +} + +template +CmdType* CommandContainer::addNamedCommand(Args... p) +{ + auto cmd = new CmdType(m_appContext, p...); + this->addCommand_impl(CmdType::Name, cmd); + return cmd; +} + } // namespace Mayo diff --git a/src/app/commands_display.cpp b/src/app/commands_display.cpp index 1b021a6b..9cdea974 100644 --- a/src/app/commands_display.cpp +++ b/src/app/commands_display.cpp @@ -23,8 +23,19 @@ namespace Mayo { -CommandChangeProjection::CommandChangeProjection(IAppContext* context) +BaseCommandDisplay::BaseCommandDisplay(IAppContext* context) : Command(context) +{ +} + +bool BaseCommandDisplay::getEnabledStatus() const +{ + return this->app()->documentCount() != 0 + && this->context()->currentPage() == IAppContext::Page::Documents; +} + +CommandChangeProjection::CommandChangeProjection(IAppContext* context) + : BaseCommandDisplay(context) { m_actionOrtho = new QAction(Command::tr("Orthographic"), this); m_actionPersp = new QAction(Command::tr("Perspective"), this); @@ -67,11 +78,6 @@ void CommandChangeProjection::execute() { } -bool CommandChangeProjection::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - void CommandChangeProjection::onCurrentDocumentChanged() { const GuiDocument* guiDoc = this->currentGuiDocument(); @@ -89,7 +95,7 @@ void CommandChangeProjection::onCurrentDocumentChanged() } CommandChangeDisplayMode::CommandChangeDisplayMode(IAppContext* context) - : Command(context) + : BaseCommandDisplay(context) { auto action = new QAction(this); action->setText(Command::tr("Mode")); @@ -109,11 +115,6 @@ void CommandChangeDisplayMode::execute() { } -bool CommandChangeDisplayMode::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - void CommandChangeDisplayMode::recreateMenuDisplayMode() { QMenu* menu = this->action()->menu(); @@ -156,7 +157,7 @@ void CommandChangeDisplayMode::recreateMenuDisplayMode() } CommandToggleOriginTrihedron::CommandToggleOriginTrihedron(IAppContext* context) - : Command(context) + : BaseCommandDisplay(context) { auto action = new QAction(this); action->setText(Command::tr("Show Origin Trihedron")); @@ -180,11 +181,6 @@ void CommandToggleOriginTrihedron::execute() } } -bool CommandToggleOriginTrihedron::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - void CommandToggleOriginTrihedron::onCurrentDocumentChanged() { GuiDocument* guiDoc = this->currentGuiDocument(); @@ -199,7 +195,7 @@ void CommandToggleOriginTrihedron::onCurrentDocumentChanged() } CommandTogglePerformanceStats::CommandTogglePerformanceStats(IAppContext* context) - : Command(context) + : BaseCommandDisplay(context) { auto action = new QAction(this); action->setText(Command::tr("Show Performance Stats")); @@ -218,16 +214,11 @@ void CommandTogglePerformanceStats::execute() { GuiDocument* guiDoc = this->currentGuiDocument(); if (guiDoc) { - CppUtils::toggle(guiDoc->v3dView()->ChangeRenderingParams().ToShowStats); + CppUtils::toggle(guiDoc->graphicsView()->ChangeRenderingParams().ToShowStats); guiDoc->graphicsView().redraw(); } } -bool CommandTogglePerformanceStats::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - void CommandTogglePerformanceStats::onCurrentDocumentChanged() { GuiDocument* guiDoc = this->currentGuiDocument(); @@ -242,7 +233,7 @@ void CommandTogglePerformanceStats::onCurrentDocumentChanged() } CommandZoomInCurrentDocument::CommandZoomInCurrentDocument(IAppContext* context) - : Command(context) + : BaseCommandDisplay(context) { auto action = new QAction(this); action->setText(Command::tr("Zoom In")); @@ -258,13 +249,8 @@ void CommandZoomInCurrentDocument::execute() ctrl->zoomIn(); } -bool CommandZoomInCurrentDocument::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - CommandZoomOutCurrentDocument::CommandZoomOutCurrentDocument(IAppContext* context) - : Command(context) + : BaseCommandDisplay(context) { auto action = new QAction(this); action->setText(Command::tr("Zoom Out")); @@ -280,13 +266,8 @@ void CommandZoomOutCurrentDocument::execute() ctrl->zoomOut(); } -bool CommandZoomOutCurrentDocument::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - CommandTurnViewCounterClockWise::CommandTurnViewCounterClockWise(IAppContext* context) - : Command(context) + : BaseCommandDisplay(context) { auto action = new QAction(this); action->setText(Command::tr("Turn Counter Clockwise")); @@ -303,13 +284,8 @@ void CommandTurnViewCounterClockWise::execute() ctrl->turn(V3d_Z, -increment); } -bool CommandTurnViewCounterClockWise::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - CommandTurnViewClockWise::CommandTurnViewClockWise(IAppContext* context) - : Command(context) + : BaseCommandDisplay(context) { auto action = new QAction(this); action->setText(Command::tr("Turn Clockwise")); @@ -326,9 +302,4 @@ void CommandTurnViewClockWise::execute() ctrl->turn(V3d_Z, increment); } -bool CommandTurnViewClockWise::getEnabledStatus() const -{ - return this->app()->documentCount() != 0; -} - } // namespace Mayo diff --git a/src/app/commands_display.h b/src/app/commands_display.h index 14dc6d50..7432fffc 100644 --- a/src/app/commands_display.h +++ b/src/app/commands_display.h @@ -8,15 +8,24 @@ #include "commands_api.h" +#include + class QMenu; namespace Mayo { -class CommandChangeProjection : public Command { +class BaseCommandDisplay : public Command { +public: + BaseCommandDisplay(IAppContext* context); + bool getEnabledStatus() const override; +}; + +class CommandChangeProjection : public BaseCommandDisplay { public: CommandChangeProjection(IAppContext* context); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "change-projection"; private: void onCurrentDocumentChanged(); @@ -24,63 +33,70 @@ class CommandChangeProjection : public Command { QAction* m_actionPersp = nullptr; }; -class CommandChangeDisplayMode : public Command { +class CommandChangeDisplayMode : public BaseCommandDisplay { public: CommandChangeDisplayMode(IAppContext* context); CommandChangeDisplayMode(IAppContext* context, QMenu* containerMenu); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "change-display-mode"; private: void recreateMenuDisplayMode(); }; -class CommandToggleOriginTrihedron : public Command { +class CommandToggleOriginTrihedron : public BaseCommandDisplay { public: CommandToggleOriginTrihedron(IAppContext* context); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "toggle-origin-trihedron"; private: void onCurrentDocumentChanged(); }; -class CommandTogglePerformanceStats : public Command { +class CommandTogglePerformanceStats : public BaseCommandDisplay { public: CommandTogglePerformanceStats(IAppContext* context); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "toggle-performance-stats"; private: void onCurrentDocumentChanged(); }; -class CommandZoomInCurrentDocument : public Command { +class CommandZoomInCurrentDocument : public BaseCommandDisplay { public: CommandZoomInCurrentDocument(IAppContext* context); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "current-doc-zoom-in"; }; -class CommandZoomOutCurrentDocument : public Command { +class CommandZoomOutCurrentDocument : public BaseCommandDisplay { public: CommandZoomOutCurrentDocument(IAppContext* context); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "current-doc-zoom-out"; }; -class CommandTurnViewCounterClockWise : public Command { +class CommandTurnViewCounterClockWise : public BaseCommandDisplay { public: CommandTurnViewCounterClockWise(IAppContext* context); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "current-doc-turn-view-ccw"; }; -class CommandTurnViewClockWise : public Command { +class CommandTurnViewClockWise : public BaseCommandDisplay { public: CommandTurnViewClockWise(IAppContext* context); void execute() override; - bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "current-doc-turn-view-cw"; }; } // namespace Mayo diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp index 133d4d82..e8a77db9 100644 --- a/src/app/commands_file.cpp +++ b/src/app/commands_file.cpp @@ -15,6 +15,7 @@ #include "recent_files.h" #include "theme.h" +#include #include #include #include @@ -167,6 +168,7 @@ void FileCommandTools::closeDocument(IAppContext* context, Document::Identifier void FileCommandTools::openDocumentsFromList(IAppContext* context, Span listFilePath) { + assert(context != nullptr); auto app = context->guiApp()->application(); auto appModule = AppModule::get(); for (const FilePath& fp : listFilePath) { @@ -312,7 +314,7 @@ void CommandRecentFiles::recreateEntries() menu->clear(); int idFile = 0; auto appModule = AppModule::get(); - const RecentFiles& recentFiles = appModule->properties()->recentFiles.value(); + const RecentFiles& recentFiles = appModule->properties()->recentFiles; for (const RecentFile& recentFile : recentFiles) { const QString strFilePath = filepathTo(recentFile.filepath); const QString strEntryRecentFile = Command::tr("%1 | %2").arg(++idFile).arg(strFilePath); @@ -384,7 +386,8 @@ void CommandImportInCurrentDocument::execute() bool CommandImportInCurrentDocument::getEnabledStatus() const { - return this->app()->documentCount() != 0; + return this->app()->documentCount() != 0 + && this->context()->currentPage() == IAppContext::Page::Documents; } CommandExportSelectedApplicationItems::CommandExportSelectedApplicationItems(IAppContext* context) @@ -416,7 +419,8 @@ void CommandExportSelectedApplicationItems::execute() Command::tr("Select Output File"), filepathTo(lastSettings.openDir), listWriterFileFilter.join(QLatin1String(";;")), - &lastSettings.selectedFilter); + &lastSettings.selectedFilter + ); if (strFilepath.isEmpty()) return; @@ -444,7 +448,8 @@ void CommandExportSelectedApplicationItems::execute() bool CommandExportSelectedApplicationItems::getEnabledStatus() const { - return this->app()->documentCount() != 0; + return this->app()->documentCount() != 0 + && this->context()->currentPage() == IAppContext::Page::Documents; } CommandCloseCurrentDocument::CommandCloseCurrentDocument(IAppContext* context) @@ -485,7 +490,7 @@ void CommandCloseCurrentDocument::updateActionText(Document::Identifier docId) const QString docName = to_QString(docPtr ? docPtr->name() : std::string{}); const QString textActionClose = docPtr ? - Command::tr("Close \"%1\"").arg(strFilepathQuoted(docName)) : + Command::tr("Close %1").arg(strFilepathQuoted(docName)) : Command::tr("Close"); this->action()->setText(textActionClose); this->action()->setToolTip(textActionClose); @@ -556,7 +561,7 @@ void CommandCloseAllDocumentsExceptCurrent::updateActionText(Document::Identifie const QString docName = to_QString(docPtr ? docPtr->name() : std::string{}); const QString textActionClose = docPtr ? - Command::tr("Close all except \"%1\"").arg(strFilepathQuoted(docName)) : + Command::tr("Close all except %1").arg(strFilepathQuoted(docName)) : Command::tr("Close all except current"); this->action()->setText(textActionClose); } diff --git a/src/app/commands_file.h b/src/app/commands_file.h index bc06dcf7..d984e978 100644 --- a/src/app/commands_file.h +++ b/src/app/commands_file.h @@ -23,6 +23,8 @@ class CommandNewDocument : public Command { public: CommandNewDocument(IAppContext* context); void execute() override; + + static constexpr std::string_view Name = "new-doc"; }; class CommandOpenDocuments : public Command { @@ -30,6 +32,8 @@ class CommandOpenDocuments : public Command { CommandOpenDocuments(IAppContext* context); void execute() override; bool eventFilter(QObject* watched, QEvent* event) override; + + static constexpr std::string_view Name = "open-docs"; }; class CommandRecentFiles : public Command { @@ -38,6 +42,8 @@ class CommandRecentFiles : public Command { CommandRecentFiles(IAppContext* context, QMenu* containerMenu); void execute() override; void recreateEntries(); + + static constexpr std::string_view Name = "recent-files"; }; class CommandImportInCurrentDocument : public Command { @@ -45,6 +51,8 @@ class CommandImportInCurrentDocument : public Command { CommandImportInCurrentDocument(IAppContext* context); void execute() override; bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "import"; }; class CommandExportSelectedApplicationItems : public Command { @@ -52,6 +60,8 @@ class CommandExportSelectedApplicationItems : public Command { CommandExportSelectedApplicationItems(IAppContext* context); void execute() override; bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "export"; }; class CommandCloseCurrentDocument : public Command { @@ -60,6 +70,8 @@ class CommandCloseCurrentDocument : public Command { void execute() override; bool getEnabledStatus() const override; + static constexpr std::string_view Name = "close-doc"; + private: void updateActionText(Document::Identifier docId); }; @@ -69,6 +81,8 @@ class CommandCloseAllDocuments : public Command { CommandCloseAllDocuments(IAppContext* context); void execute() override; bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "close-all-docs"; }; class CommandCloseAllDocumentsExceptCurrent : public Command { @@ -77,6 +91,8 @@ class CommandCloseAllDocumentsExceptCurrent : public Command { void execute() override; bool getEnabledStatus() const override; + static constexpr std::string_view Name = "close-all-docs-except-current"; + private: void updateActionText(Document::Identifier docId); }; @@ -85,6 +101,8 @@ class CommandQuitApplication : public Command { public: CommandQuitApplication(IAppContext* context); void execute() override; + + static constexpr std::string_view Name = "quit-app"; }; } // namespace Mayo diff --git a/src/app/commands_help.cpp b/src/app/commands_help.cpp index b7fe8f3f..92cb882e 100644 --- a/src/app/commands_help.cpp +++ b/src/app/commands_help.cpp @@ -8,10 +8,12 @@ #include "dialog_about.h" #include "qtwidgets_utils.h" +#include "qstring_conv.h" +#include #include #include -#include +#include namespace Mayo { @@ -32,13 +34,16 @@ CommandAbout::CommandAbout(IAppContext* context) : Command(context) { auto action = new QAction(this); - action->setText(Command::tr("About %1").arg(QApplication::applicationName())); + action->setText(Command::tr("About %1").arg(QCoreApplication::applicationName())); this->setAction(action); } void CommandAbout::execute() { auto dlg = new DialogAbout(this->widgetMain()); + for (const auto& libInfo : CommandSystemInformation::libraryInfos()) + dlg->addLibraryInfo(libInfo.name, libInfo.version); + QtWidgetsUtils::asyncDialogExec(dlg); } diff --git a/src/app/commands_help.h b/src/app/commands_help.h index ca880669..23c49a92 100644 --- a/src/app/commands_help.h +++ b/src/app/commands_help.h @@ -7,6 +7,7 @@ #pragma once #include "commands_api.h" +#include "../base/span.h" namespace Mayo { @@ -14,12 +15,38 @@ class CommandReportBug : public Command { public: CommandReportBug(IAppContext* context); void execute() override; + + static constexpr std::string_view Name = "report-bug"; +}; + +class CommandSystemInformation : public Command { +public: + CommandSystemInformation(IAppContext* context); + void execute() override; + + static QString data(); + + struct LibraryInfo { + std::string name; + std::string version; + std::string versionDetails; + }; + static void addLibraryInfo( + std::string_view libName, + std::string_view version, + std::string_view versionDetails = "" + ); + static Span libraryInfos(); + + static constexpr std::string_view Name = "system-info"; }; class CommandAbout : public Command { public: CommandAbout(IAppContext* context); void execute() override; + + static constexpr std::string_view Name = "about"; }; } // namespace Mayo diff --git a/src/app/commands_tools.cpp b/src/app/commands_tools.cpp index a8d52bb9..fd8af3c3 100644 --- a/src/app/commands_tools.cpp +++ b/src/app/commands_tools.cpp @@ -40,7 +40,8 @@ void CommandSaveViewImage::execute() bool CommandSaveViewImage::getEnabledStatus() const { - return this->app()->documentCount() != 0; + return this->app()->documentCount() != 0 + && this->context()->currentPage() == IAppContext::Page::Documents; } CommandInspectXde::CommandInspectXde(IAppContext* context) @@ -77,7 +78,8 @@ bool CommandInspectXde::getEnabledStatus() const !spanSelectedAppItem.empty() ? spanSelectedAppItem.front() : ApplicationItem(); return spanSelectedAppItem.size() == 1 && firstAppItem.isValid() - && firstAppItem.document()->isXCafDocument(); + && firstAppItem.document()->isXCafDocument() + && this->context()->currentPage() == IAppContext::Page::Documents; } CommandEditOptions::CommandEditOptions(IAppContext* context) diff --git a/src/app/commands_tools.h b/src/app/commands_tools.h index 5e3e803e..9ade0add 100644 --- a/src/app/commands_tools.h +++ b/src/app/commands_tools.h @@ -15,6 +15,8 @@ class CommandSaveViewImage : public Command { CommandSaveViewImage(IAppContext* context); void execute() override; bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "save-view-image"; }; class CommandInspectXde : public Command { @@ -22,12 +24,16 @@ class CommandInspectXde : public Command { CommandInspectXde(IAppContext* context); void execute() override; bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "inspect-xde"; }; class CommandEditOptions : public Command { public: CommandEditOptions(IAppContext* context); void execute() override; + + static constexpr std::string_view Name = "edit-options"; }; } // namespace Mayo diff --git a/src/app/commands_window.cpp b/src/app/commands_window.cpp index 971497d2..ad939fbf 100644 --- a/src/app/commands_window.cpp +++ b/src/app/commands_window.cpp @@ -48,33 +48,30 @@ CommandLeftSidebarWidgetToggle::CommandLeftSidebarWidgetToggle(IAppContext* cont action->setToolTip(Command::tr("Show/Hide Left Sidebar")); action->setShortcut(Qt::ALT + Qt::Key_0); action->setCheckable(true); - action->setChecked(context->widgetLeftSidebar()->isVisible()); + action->setChecked(context->pageDocuments_widgetLeftSideBar()->isVisible()); this->setAction(action); this->updateAction(); - context->widgetLeftSidebar()->installEventFilter(this); + context->pageDocuments_widgetLeftSideBar()->installEventFilter(this); } void CommandLeftSidebarWidgetToggle::execute() { - const bool isVisible = this->context()->widgetLeftSidebar()->isVisible(); - this->context()->widgetLeftSidebar()->setVisible(!isVisible); + QWidget* widget = this->context()->pageDocuments_widgetLeftSideBar(); + widget->setVisible(!widget->isVisible()); } bool CommandLeftSidebarWidgetToggle::getEnabledStatus() const { - return this->context()->modeWidgetMain() != IAppContext::ModeWidgetMain::Home; + return this->context()->currentPage() != IAppContext::Page::Home; } bool CommandLeftSidebarWidgetToggle::eventFilter(QObject* watched, QEvent* event) { - if (watched == this->context()->widgetLeftSidebar()) { - if (event->type() == QEvent::Show || event->type() == QEvent::Hide) { + if (event->type() == QEvent::Show || event->type() == QEvent::Hide) { + if (watched == this->context()->pageDocuments_widgetLeftSideBar()) { this->updateAction(); return true; } - else { - return false; - } } return Command::eventFilter(watched, event); @@ -82,7 +79,7 @@ bool CommandLeftSidebarWidgetToggle::eventFilter(QObject* watched, QEvent* event void CommandLeftSidebarWidgetToggle::updateAction() { - if (this->context()->widgetLeftSidebar()->isVisible()) { + if (this->context()->pageDocuments_widgetLeftSideBar()->isVisible()) { this->action()->setText(Command::tr("Hide Left Sidebar")); this->action()->setIcon(mayoTheme()->icon(Theme::Icon::BackSquare)); } @@ -94,6 +91,69 @@ void CommandLeftSidebarWidgetToggle::updateAction() this->action()->setToolTip(this->action()->text()); } +CommandSwitchMainWidgetMode::CommandSwitchMainWidgetMode(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setToolTip(Command::tr("Go To Home Page")); + action->setShortcut(Qt::CTRL + Qt::Key_0); + this->setAction(action); + this->updateAction(); + context->widgetPage(IAppContext::Page::Home)->installEventFilter(this); + context->widgetPage(IAppContext::Page::Documents)->installEventFilter(this); +} + +void CommandSwitchMainWidgetMode::execute() +{ + auto newPage = IAppContext::Page::Unknown; + switch (this->context()->currentPage()) { + case IAppContext::Page::Home: + newPage = IAppContext::Page::Documents; + break; + case IAppContext::Page::Documents: + newPage = IAppContext::Page::Home; + break; + case IAppContext::Page::Unknown: + break; + } + + this->context()->setCurrentPage(newPage); + this->context()->updateControlsEnabledStatus(); +} + +bool CommandSwitchMainWidgetMode::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +bool CommandSwitchMainWidgetMode::eventFilter(QObject* watched, QEvent* event) +{ + if (event->type() == QEvent::Show) { + if (watched == this->context()->widgetPage(IAppContext::Page::Home) + || watched == this->context()->widgetPage(IAppContext::Page::Documents)) + { + this->updateAction(); + return true; + } + } + + return Command::eventFilter(watched, event); +} + +void CommandSwitchMainWidgetMode::updateAction() +{ + switch (this->context()->currentPage()) { + case IAppContext::Page::Home: + this->action()->setText(Command::tr("Go To Documents")); + break; + case IAppContext::Page::Documents: + this->action()->setText(Command::tr("Go To Home Page")); + break; + case IAppContext::Page::Unknown: + break; + } +} + CommandPreviousDocument::CommandPreviousDocument(IAppContext* context) : Command(context) { @@ -113,7 +173,9 @@ void CommandPreviousDocument::execute() bool CommandPreviousDocument::getEnabledStatus() const { - return this->app()->documentCount() != 0 && this->currentDocumentIndex() > 0; + return this->app()->documentCount() != 0 + && this->context()->currentPage() == IAppContext::Page::Documents + && this->currentDocumentIndex() > 0; } CommandNextDocument::CommandNextDocument(IAppContext* context) @@ -136,7 +198,9 @@ void CommandNextDocument::execute() bool CommandNextDocument::getEnabledStatus() const { const int appDocumentCount = this->app()->documentCount(); - return appDocumentCount != 0 && this->currentDocumentIndex() < appDocumentCount - 1; + return appDocumentCount != 0 + && this->context()->currentPage() == IAppContext::Page::Documents + && this->currentDocumentIndex() < appDocumentCount - 1; } } // namespace Mayo diff --git a/src/app/commands_window.h b/src/app/commands_window.h index 008ffbc8..1488fdef 100644 --- a/src/app/commands_window.h +++ b/src/app/commands_window.h @@ -15,18 +15,39 @@ class CommandMainWidgetToggleFullscreen : public Command { CommandMainWidgetToggleFullscreen(IAppContext* context); void execute() override; + static constexpr std::string_view Name = "toggle-fullscreen"; + private: Qt::WindowStates m_previousWindowState = Qt::WindowNoState; }; +// Provides command to toggle visibility of the left-side bar widget belonging to +// the "Documents" main page class CommandLeftSidebarWidgetToggle : public Command { public: CommandLeftSidebarWidgetToggle(IAppContext* context); + + void execute() override; + bool getEnabledStatus() const override; + + bool eventFilter(QObject* watched, QEvent* event) override; + + static constexpr std::string_view Name = "toggle-left-sidebar"; + +private: + void updateAction(); +}; + +class CommandSwitchMainWidgetMode : public Command { +public: + CommandSwitchMainWidgetMode(IAppContext* context); void execute() override; bool getEnabledStatus() const override; bool eventFilter(QObject* watched, QEvent* event) override; + static constexpr std::string_view Name = "switch-main-widget-mode"; + private: void updateAction(); }; @@ -36,6 +57,8 @@ class CommandPreviousDocument : public Command { CommandPreviousDocument(IAppContext* context); void execute() override; bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "previous-doc"; }; class CommandNextDocument : public Command { @@ -43,6 +66,8 @@ class CommandNextDocument : public Command { CommandNextDocument(IAppContext* context); void execute() override; bool getEnabledStatus() const override; + + static constexpr std::string_view Name = "next-doc"; }; } // namespace Mayo diff --git a/src/app/console.cpp b/src/app/console.cpp index cc70ce10..183cac75 100644 --- a/src/app/console.cpp +++ b/src/app/console.cpp @@ -11,7 +11,7 @@ # include # include #else -# include //ioctl() and TIOCGWINSZ +# include // ioctl() and TIOCGWINSZ # include // for STDOUT_FILENO # include #endif diff --git a/src/app/dialog_about.cpp b/src/app/dialog_about.cpp index 4c11e362..86d674bb 100644 --- a/src/app/dialog_about.cpp +++ b/src/app/dialog_about.cpp @@ -7,11 +7,9 @@ #include "dialog_about.h" #include "ui_dialog_about.h" +#include "qstring_conv.h" #include "version.h" #include -#ifdef HAVE_GMIO -# include -#endif namespace Mayo { @@ -24,15 +22,13 @@ DialogAbout::DialogAbout(QWidget* parent) tr("%1 By %2").arg(QApplication::applicationName(), QApplication::organizationName()) ); - m_ui->label_Version->setText(m_ui->label_Version->text().arg(strVersion).arg(QT_POINTER_SIZE * 8)); + const QString strVersionFull = + QString("%1 commit:%2 revnum:%3") + .arg(strVersion).arg(strVersionCommitId).arg(versionRevisionNumber); + m_ui->label_Version->setText(m_ui->label_Version->text().arg(strVersionFull).arg(QT_POINTER_SIZE * 8)); m_ui->label_BuildDateTime->setText(m_ui->label_BuildDateTime->text().arg(__DATE__, __TIME__)); m_ui->label_Qt->setText(m_ui->label_Qt->text().arg(QT_VERSION_STR)); m_ui->label_Occ->setText(m_ui->label_Occ->text().arg(OCC_VERSION_COMPLETE)); -#ifdef HAVE_GMIO - m_ui->label_Gmio->setText(m_ui->label_Gmio->text().arg(GMIO_VERSION_STR)); -#else - m_ui->label_Gmio->hide(); -#endif } DialogAbout::~DialogAbout() @@ -40,4 +36,11 @@ DialogAbout::~DialogAbout() delete m_ui; } +void DialogAbout::addLibraryInfo(std::string_view libName, std::string_view libVersion) +{ + auto label = new QLabel(this); + label->setText(tr("%1 %2").arg(to_QString(libName), to_QString(libVersion))); + m_ui->layout_Infos->addWidget(label);; +} + } // namespace Mayo diff --git a/src/app/dialog_about.h b/src/app/dialog_about.h index 34bb8114..86428322 100644 --- a/src/app/dialog_about.h +++ b/src/app/dialog_about.h @@ -7,6 +7,7 @@ #pragma once #include +#include namespace Mayo { @@ -16,6 +17,8 @@ class DialogAbout : public QDialog { DialogAbout(QWidget* parent = nullptr); ~DialogAbout(); + void addLibraryInfo(std::string_view libName, std::string_view libVersion); + private: class Ui_DialogAbout* m_ui = nullptr; }; diff --git a/src/app/dialog_about.ui b/src/app/dialog_about.ui index 13f1837b..af20ac50 100644 --- a/src/app/dialog_about.ui +++ b/src/app/dialog_about.ui @@ -6,8 +6,8 @@ 0 0 - 288 - 175 + 270 + 160 @@ -35,7 +35,7 @@ - :/images/appicon_64.png + :/images/appicon_64.png Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop @@ -46,7 +46,6 @@ - 75 true @@ -86,28 +85,21 @@ - - + + Qt %1 - + OpenCascade %1 - - - - gmio %1 - - - @@ -136,7 +128,7 @@ - + diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 90ea81b6..4bdff451 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -13,6 +13,7 @@ #include "../base/settings.h" #include "../base/tkernel_utils.h" #include "app_module.h" +#include "filepath_conv.h" #include "qmeta_tdf_label.h" #include "qstring_conv.h" #include "qstring_utils.h" @@ -120,9 +121,14 @@ static void loadLabelAttributes(const TDF_Label& label, QTreeWidgetItem* treeIte } else if (attrId == TNaming_NamedShape::GetID()) { const auto& namedShape = static_cast(*ptrAttr); + const TopoDS_Shape shape = namedShape.Get(); text = "TNaming_NamedShape"; - value = DialogInspectXde::tr("ShapeType=%1, Evolution=%2") - .arg(MetaEnum::name(namedShape.Get().ShapeType()).data()) + value = DialogInspectXde::tr("ShapeType=%1, ShapeLocation=%2, Evolution=%3") + .arg(MetaEnum::name(shape.ShapeType()).data()) + .arg(shape.Location().Transformation().Form() != gp_Identity ? + QStringUtils::text(shape.Location(), appDefaultTextOptions()) + : QString("id") + ) .arg(MetaEnum::name(namedShape.Evolution()).data()); } else { @@ -295,9 +301,15 @@ static void loadLabelMaterialProperties( // This helper allows "lazy" loading of the image files class ImageFileTreeWidgetItem : public QTreeWidgetItem { public: - void setImageFilePath(int col, const QString& strFilePath) + void setImage(int col, const FilePath& filePath) { - const ItemData item{strFilePath, {}}; + const ItemData item{filePath, {}, {}}; + m_mapColumnItemData.insert({ col, item }); + } + + void setImage(int col, const QByteArray& data) + { + const ItemData item{{}, data, {}}; m_mapColumnItemData.insert({ col, item }); } @@ -312,12 +324,23 @@ class ImageFileTreeWidgetItem : public QTreeWidgetItem { return {}; if (ptrItem->strToolTip.isEmpty()) { - const QPixmap pixmap(ptrItem->strFilePath); + QPixmap pixmap; + uintmax_t imageSize = 0; + + if (!ptrItem->filePath.empty()) { + pixmap.load(filepathTo(ptrItem->filePath)); + imageSize = filepathFileSize(ptrItem->filePath); + } + else { + pixmap.loadFromData(ptrItem->fileData); + imageSize = ptrItem->fileData.size(); + } + if (!pixmap.isNull()) { QBuffer bufferPixmap; - const QPixmap pixmapClamped = pixmap.scaledToWidth(std::min(pixmap.width(), 400)); + const int pixmapWidth = std::min(pixmap.width(), int(400 * qGuiApp->devicePixelRatio())); + const QPixmap pixmapClamped = pixmap.scaledToWidth(pixmapWidth); pixmapClamped.save(&bufferPixmap, "PNG"); - const auto imageSize = QFileInfo(ptrItem->strFilePath).size(); const QString strImageSize = QStringUtils::bytesText(imageSize, appDefaultTextOptions().locale); ptrItem->strToolTip = QString("

%4

") @@ -325,7 +348,7 @@ class ImageFileTreeWidgetItem : public QTreeWidgetItem { .arg(pixmapClamped.width()) .arg(pixmapClamped.height()) .arg(DialogInspectXde::tr("File Size: %1
Dimensions: %2x%3 Depth: %4") - .arg(strImageSize).arg(pixmap.width()).arg(pixmap.height()).arg(pixmap.depth())) + .arg(strImageSize).arg(pixmap.width()).arg(pixmap.height()).arg(pixmap.depth())) ; } else { @@ -338,7 +361,8 @@ class ImageFileTreeWidgetItem : public QTreeWidgetItem { private: struct ItemData { - QString strFilePath; + FilePath filePath; + QByteArray fileData; QString strToolTip; }; @@ -350,11 +374,36 @@ static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const Handle if (imgTexture.IsNull()) return static_cast(nullptr); - const QString strTextureFilePath = to_QString(imgTexture->FilePath()); auto item = new ImageFileTreeWidgetItem; item->setText(0, text); - item->setText(1, strTextureFilePath); - item->setImageFilePath(1, strTextureFilePath); + if (!imgTexture->FilePath().IsEmpty()) { + // Texture is provided through a file reference + const FilePath filePath = filepathCanonical(filepathFrom(imgTexture->FilePath())); + const QString strFilePath = filepathTo(filePath); + const auto fileOffset = imgTexture->FileOffset(); + if (fileOffset > 0) { + // Texture is defined in a file portion + item->setText(1, DialogInspectXde::tr("%1,offset:%2").arg(strFilePath).arg(fileOffset)); + QFile file(strFilePath); + if (file.open(QIODevice::ReadOnly)) { + file.seek(fileOffset); + const QByteArray buff = file.read(imgTexture->FileLength()); + item->setImage(1, buff); + } + } + else { + // Texture is defined in a file + item->setText(1, strFilePath); + item->setImage(1, filePath); + } + } + else if (imgTexture->DataBuffer() && !imgTexture->DataBuffer()->IsEmpty()) { + // Texture is provided by some embedded data + item->setText(1, DialogInspectXde::tr("")); + const Handle(NCollection_Buffer)& buff = imgTexture->DataBuffer(); + item->setImage(1, QByteArray::fromRawData(reinterpret_cast(buff->Data()), buff->Size())); + } + return item; } #endif @@ -378,7 +427,11 @@ static void loadLabelVisMaterialProperties( item->addChild(createPropertyTreeItem("BaseColor", material->BaseColor())); item->addChild(createPropertyTreeItem("AlphaMode", MetaEnum::name(material->AlphaMode()))); item->addChild(createPropertyTreeItem("AlphaCutOff", material->AlphaCutOff())); +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + item->addChild(createPropertyTreeItem("FaceCulling", MetaEnum::name(material->FaceCulling()))); +#else item->addChild(createPropertyTreeItem("IsDoubleSided", material->IsDoubleSided())); +#endif if (!material->RawName().IsNull()) item->addChild(createPropertyTreeItem("RawName", to_QString(material->RawName()))); diff --git a/src/app/dialog_options.cpp b/src/app/dialog_options.cpp index da01a316..10f147d4 100644 --- a/src/app/dialog_options.cpp +++ b/src/app/dialog_options.cpp @@ -251,7 +251,8 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) const QVariant variantNodeId = current.data(ItemSettingNodeId_Role); const QModelIndex indexFirst = settingsModel->index(0, 0); const QModelIndexList indexList = settingsModel->match( - indexFirst, ItemSettingNodeId_Role, variantNodeId, 1, Qt::MatchExactly); + indexFirst, ItemSettingNodeId_Role, variantNodeId, 1, Qt::MatchExactly + ); if (!indexList.isEmpty()) m_ui->listWidget_Settings->scrollTo(indexList.front(), QAbstractItemView::PositionAtTop); }); diff --git a/src/app/gui_document_list_model.cpp b/src/app/gui_document_list_model.cpp index 5bbd27c3..7ebb5f9c 100644 --- a/src/app/gui_document_list_model.cpp +++ b/src/app/gui_document_list_model.cpp @@ -6,6 +6,8 @@ #include "gui_document_list_model.h" +#include "filepath_conv.h" +#include "qstring_conv.h" #include "../base/application.h" #include "../base/document.h" #include "../gui/gui_application.h" @@ -28,18 +30,18 @@ GuiDocumentListModel::GuiDocumentListModel(const GuiApplication* guiApp, QObject QVariant GuiDocumentListModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= this->rowCount()) - return QVariant(); + return {}; const DocumentPtr& doc = m_vecGuiDocument.at(index.row())->document(); switch (role) { case Qt::ToolTipRole: - return QString::fromStdString(doc->filePath().u8string()); + return filepathTo(filepathCanonical(doc->filePath())); case Qt::DisplayRole: case Qt::EditRole: - return QString::fromStdString(doc->name()); + return to_QString(doc->name()); } - return QVariant(); + return {}; } int GuiDocumentListModel::rowCount(const QModelIndex& /*parent*/) const @@ -49,7 +51,9 @@ int GuiDocumentListModel::rowCount(const QModelIndex& /*parent*/) const void GuiDocumentListModel::appendGuiDocument(const GuiDocument* guiDoc) { - const int row = this->rowCount(); + // NOTE: don't use rowCount() as it's virtual and appendGuiDocument() is called in constructor + // of this class(virtual dispatch would be bypassed) + const auto row = int(m_vecGuiDocument.size()); this->beginInsertRows(QModelIndex(), row, row); m_vecGuiDocument.emplace_back(guiDoc); this->endInsertRows(); @@ -71,7 +75,8 @@ void GuiDocumentListModel::onDocumentNameChanged(const DocumentPtr& doc, const s auto itFound = std::find_if( m_vecGuiDocument.cbegin(), m_vecGuiDocument.cend(), - [&](const GuiDocument* guiDoc) { return guiDoc->document() == doc; }); + [&](const GuiDocument* guiDoc) { return guiDoc->document() == doc; } + ); if (itFound != m_vecGuiDocument.cend()) { const int row = itFound - m_vecGuiDocument.begin(); const QModelIndex itemIndex = this->index(row); diff --git a/src/app/iwidget_main_page.h b/src/app/iwidget_main_page.h new file mode 100644 index 00000000..7987fda4 --- /dev/null +++ b/src/app/iwidget_main_page.h @@ -0,0 +1,39 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include + +namespace Mayo { + +class CommandContainer; + +// Provides an interface for main pages within the Mayo application +// As its core, Mayo UI is basically a stack of widgets +// Such widgets are called "main pages" or just "pages" and only a single one is active at a time +class IWidgetMainPage : public QWidget { + Q_OBJECT +public: + // Builds UI objects(eg this might calls setupUi() on Qt-generated widgets) + IWidgetMainPage(QWidget* parent = nullptr) + : QWidget(parent) + {} + + // Completes initialization of the page(eg by linking some buttons to existing commands) + virtual void initialize(const CommandContainer* cmdContainer) = 0; + + // Update the activation("enabled" status) of the controls(ie any widget) belonging to this page + virtual void updatePageControlsActivation() = 0; + +signals: + // Signal emitted when a "global" or "complete" activation at the whole application level is + // required by this page + // This might, for example, be consecutive to event internal to this page + void updateGlobalControlsActivationRequired(); +}; + +} // namespace Mayo diff --git a/src/app/main.cpp b/src/app/main.cpp index 76e4b510..d7eed7ab 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -8,6 +8,7 @@ #include "../base/document_tree_node_properties_provider.h" #include "../base/io_system.h" #include "../base/settings.h" +#include "../io_assimp/io_assimp.h" #include "../io_dxf/io_dxf.h" #include "../io_gmio/io_gmio.h" #include "../io_image/io_image.h" @@ -22,6 +23,7 @@ #include "../gui/gui_application.h" #include "app_module.h" #include "cli_export.h" +#include "commands_help.h" #include "console.h" #include "document_tree_node_properties_providers.h" #include "filepath_conv.h" @@ -82,6 +84,7 @@ struct CommandLineArguments { std::vector listFilepathToExport; std::vector listFilepathToOpen; bool cliProgressReport = true; + bool showSystemInformation = false; }; // Provides customization of Qt message handler @@ -228,6 +231,12 @@ static CommandLineArguments processCommandLine() ); cmdParser.addOption(cmdCliNoProgress); + const QCommandLineOption cmdSysInfo( + QStringList{ "system-info" }, + Main::tr("Show detailed system information and quit") + ); + cmdParser.addOption(cmdSysInfo); + cmdParser.addPositionalArgument( Main::tr("files"), Main::tr("Files to open at startup, optionally"), @@ -268,6 +277,7 @@ static CommandLineArguments processCommandLine() args.includeDebugLogs = cmdParser.isSet(cmdDebugLogs); #endif args.cliProgressReport = !cmdParser.isSet(cmdCliNoProgress); + args.showSystemInformation = cmdParser.isSet(cmdSysInfo); return args; } @@ -342,7 +352,7 @@ static std::string_view qtTranslate(const TextId& text, int n) } // Helper to query the OpenGL version string -static std::string queryGlVersionString() +[[maybe_unused]] static std::string queryGlVersionString() { QOpenGLContext glContext; if (!glContext.create()) @@ -362,7 +372,7 @@ static std::string queryGlVersionString() // Helper to parse a string containing a semantic version eg "4.6.5 CodeNamed" // Note: only major and minor versions are detected -static QVersionNumber parseSemanticVersionString(std::string_view strVersion) +[[maybe_unused]] static QVersionNumber parseSemanticVersionString(std::string_view strVersion) { if (strVersion.empty()) return {}; @@ -396,7 +406,6 @@ static void initGui(GuiApplication* guiApp) if (!propForceOpenGlFallbackWidget && hasQGuiApplication) { // QOpenGL requires QGuiApplication const std::string strGlVersion = queryGlVersionString(); const QVersionNumber glVersion = parseSemanticVersionString(strGlVersion); - qInfo() << fmt::format("OpenGL v{}.{}", glVersion.majorVersion(), glVersion.minorVersion()).c_str(); if (!glVersion.isNull() && glVersion.majorVersion() >= 2) { // Requires at least OpenGL version >= 2.0 setFunctionCreateGraphicsDriver(&QOpenGLWidgetOccView::createCompatibleGraphicsDriver); IWidgetOccView::setCreator(&QOpenGLWidgetOccView::create); @@ -452,12 +461,16 @@ static int runApp(QCoreApplication* qtApp) appModule->settings()->setStorage(std::make_unique()); { // Load translation files - const QString qmFilePath = QString(":/i18n/mayo_%1.qm").arg(appModule->languageCode()); - auto translator = new QTranslator(qtApp); - if (translator->load(qmFilePath)) - qtApp->installTranslator(translator); - else - qWarning() << Main::tr("Failed to load translation file [path=%1]").arg(qmFilePath); + auto fnLoadQmFile = [=](const QString& qmFilePath) { + auto translator = new QTranslator(qtApp); + if (translator->load(qmFilePath)) + qtApp->installTranslator(translator); + else + qWarning() << Main::tr("Failed to load translation file [path=%1]").arg(qmFilePath); + }; + const QString appLangCode = appModule->languageCode(); + fnLoadQmFile(QString(":/i18n/mayo_%1.qm").arg(appLangCode)); + fnLoadQmFile(QString(":/i18n/qtbase_%1.qm").arg(appLangCode)); } // Initialize Base application @@ -480,6 +493,7 @@ static int runApp(QCoreApplication* qtApp) ioSystem->addFactoryReader(std::make_unique()); ioSystem->addFactoryReader(std::make_unique()); ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(IO::AssimpFactoryReader::create()); ioSystem->addFactoryWriter(std::make_unique()); ioSystem->addFactoryWriter(std::make_unique()); ioSystem->addFactoryWriter(std::make_unique()); @@ -489,7 +503,21 @@ static int runApp(QCoreApplication* qtApp) appModule->properties()->IO_bindParameters(ioSystem); appModule->properties()->retranslate(); + // Register library infos + CommandSystemInformation::addLibraryInfo( + IO::AssimpLib::strName(), IO::AssimpLib::strVersion(), IO::AssimpLib::strVersionDetails() + ); + CommandSystemInformation::addLibraryInfo( + IO::GmioLib::strName(), IO::GmioLib::strVersion(), IO::GmioLib::strVersionDetails() + ); + // Process CLI + if (args.showSystemInformation) { + CommandSystemInformation cmdSysInfo(nullptr); + cmdSysInfo.execute(); + return qtApp->exec(); + } + if (!args.listFilepathToExport.empty()) { if (args.listFilepathToOpen.empty()) fnCriticalExit(Main::tr("No input files -> nothing to export")); @@ -553,16 +581,10 @@ int main(int argc, char* argv[]) { qInstallMessageHandler(&Mayo::LogMessageHandler::qtHandler); - // OpenCascade TKOpenGl depends on XLib for Linux(excepting Android) and BSD systems(excepting macOS) - // See for example implementation of Aspect_DisplayConnection where XLib is explicitly used - // On systems running eg Wayland this would cause problems(see https://github.com/fougue/mayo/issues/178) - // As a workaround the Qt platform is forced to xcb -#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_BSD4) && !defined(Q_OS_MACOS)) - qputenv("QT_QPA_PLATFORM", "xcb"); -#endif - // Helper function to check if application arguments contain any option listed in 'listOption' - auto fnArgsContainAnyOf = [=](std::initializer_list listOption) { + // IMPORTANT: capture by reference, because QApplication constructor may alter argc(due to + // parsing of arguments) + auto fnArgsContainAnyOf = [&](std::initializer_list listOption) { for (int i = 1; i < argc; ++i) { for (const char* option : listOption) { if (std::strcmp(argv[i], option) == 0) @@ -572,6 +594,24 @@ int main(int argc, char* argv[]) return false; }; + // If the arguments(argv) contain any of the following option, then Mayo has to run in CLI mode + const bool isAppCliMode = fnArgsContainAnyOf({ "-e", "--export", "-h", "--help", "-v", "--version" }); + + // OpenCascade TKOpenGl depends on XLib for Linux(excepting Android) and BSD systems(excepting macOS) + // See for example implementation of Aspect_DisplayConnection where XLib is explicitly used + // On systems running eg Wayland this would cause problems(see https://github.com/fougue/mayo/issues/178) + // As a workaround the Qt platform is forced to xcb +#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_BSD4) && !defined(Q_OS_MACOS)) + if ( + !isAppCliMode + && !qEnvironmentVariableIsSet("QT_QPA_PLATFORM") + && !fnArgsContainAnyOf({ "-platform" }) + ) + { + qputenv("QT_QPA_PLATFORM", "xcb"); + } +#endif + // Configure and create Qt application object #if defined(Q_OS_WIN) // Never use ANGLE on Windows, since OCCT 3D Viewer does not expect this @@ -581,7 +621,6 @@ int main(int argc, char* argv[]) QCoreApplication::setOrganizationDomain("www.fougue.pro"); QCoreApplication::setApplicationName("Mayo"); QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); - const bool isAppCliMode = fnArgsContainAnyOf({ "-e", "--export", "-h", "--help", "-v", "--version" }); std::unique_ptr ptrApp( isAppCliMode ? new QCoreApplication(argc, argv) : new QApplication(argc, argv) ); diff --git a/src/app/mainwindow.cpp b/src/app/mainwindow.cpp index ce0c4531..2d8e10f9 100644 --- a/src/app/mainwindow.cpp +++ b/src/app/mainwindow.cpp @@ -8,13 +8,7 @@ #include "ui_mainwindow.h" #include "../base/application.h" -#include "../base/application_item_selection_model.h" -#include "../base/document.h" #include "../base/global.h" -#include "../base/messenger.h" -#include "../base/settings.h" -#include "../graphics/graphics_object_driver.h" -#include "../graphics/graphics_utils.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" #include "app_context.h" @@ -25,175 +19,58 @@ #include "commands_window.h" #include "commands_help.h" #include "dialog_task_manager.h" -#include "document_property_group.h" -#include "filepath_conv.h" -#include "gui_document_list_model.h" -#include "item_view_buttons.h" -#include "qstring_conv.h" #include "qtgui_utils.h" #include "qtwidgets_utils.h" #include "theme.h" -#include "widget_file_system.h" -#include "widget_gui_document.h" +#include "widget_main_control.h" +#include "widget_main_home.h" #include "widget_message_indicator.h" -#include "widget_model_tree.h" -#include "widget_occ_view.h" -#include "widget_properties_editor.h" #ifdef Q_OS_WIN # include "windows/win_taskbar_global_progress.h" #endif -#include #include namespace Mayo { -MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) +MainWindow::MainWindow(GuiApplication* guiApp, QWidget* parent) : QMainWindow(parent), m_guiApp(guiApp), m_ui(new Ui_MainWindow) { m_ui->setupUi(this); - m_ui->widget_ModelTree->registerGuiApplication(guiApp); - - m_ui->splitter_Main->setChildrenCollapsible(false); - m_ui->splitter_Main->setStretchFactor(0, 1); - m_ui->splitter_Main->setStretchFactor(1, 3); - - m_ui->splitter_ModelTree->setStretchFactor(0, 1); - m_ui->splitter_ModelTree->setStretchFactor(1, 2); - - m_ui->stack_LeftContents->setCurrentIndex(0); - - m_ui->widget_Properties->setRowHeightFactor(1.4); - m_ui->widget_Properties->clear(); - - mayoTheme()->setupHeaderComboBox(m_ui->combo_LeftContents); - mayoTheme()->setupHeaderComboBox(m_ui->combo_GuiDocuments); + this->addPage(IAppContext::Page::Home, new WidgetMainHome(this)); + this->addPage(IAppContext::Page::Documents, new WidgetMainControl(guiApp, this)); + // AppContext requires WidgetMainControl object, ensure it has been created beforehand m_appContext = new AppContext(this); + m_cmdContainer.setAppContext(m_appContext); + + // Some commands requires WidgetMainControl UI page to exist, ensure it has been created beforehand this->createCommands(); this->createMenus(); - m_ui->btn_PreviousGuiDocument->setDefaultAction(this->getCommandAction("previous-doc")); - m_ui->btn_NextGuiDocument->setDefaultAction(this->getCommandAction("next-doc")); - m_ui->btn_CloseGuiDocument->setDefaultAction(this->getCommandAction("close-doc")); - m_ui->btn_CloseLeftSideBar->setDefaultAction(this->getCommandAction("toggle-left-sidebar")); + // WidgetMainControl page depends on some Command objects, ensure they have been created beforehand + for (auto [code, page] : m_mapWidgetPage) + page->initialize(&m_cmdContainer); - // "HomeFiles" actions - QObject::connect( - m_ui->widget_HomeFiles, &WidgetHomeFiles::newDocumentRequested, - this->getCommand("new-doc"), &Command::execute - ); - QObject::connect( - m_ui->widget_HomeFiles, &WidgetHomeFiles::openDocumentsRequested, - this->getCommand("open-docs"), &Command::execute - ); - QObject::connect( - m_ui->widget_HomeFiles, &WidgetHomeFiles::recentFileOpenRequested, - this, &MainWindow::openDocument - ); - // "Window" actions and navigation in documents - QObject::connect( - m_ui->combo_GuiDocuments, qOverload(&QComboBox::currentIndexChanged), - this, &MainWindow::onCurrentDocumentIndexChanged - ); - QObject::connect( - m_ui->widget_FileSystem, &WidgetFileSystem::locationActivated, - this, &MainWindow::onWidgetFileSystemLocationActivated - ); - // ... - QObject::connect( - m_ui->combo_LeftContents, qOverload(&QComboBox::currentIndexChanged), - this, &MainWindow::onLeftContentsPageChanged - ); - QObject::connect( - m_ui->listView_OpenedDocuments, &QListView::clicked, - this, [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); } - ); - guiApp->application()->signalDocumentFilePathChanged.connectSlot([=](const DocumentPtr& doc, const FilePath& fp) { - if (this->currentWidgetGuiDocument()->documentIdentifier() == doc->identifier()) - m_ui->widget_FileSystem->setLocation(filepathTo(fp)); - }); AppModule::get()->signalMessage.connectSlot(&MainWindow::onMessage, this); guiApp->signalGuiDocumentAdded.connectSlot(&MainWindow::onGuiDocumentAdded, this); - guiApp->selectionModel()->signalChanged.connectSlot(&MainWindow::onApplicationItemSelectionChanged, this); - // Creation of annex objects - { - // Opened documents GUI - auto listViewBtns = new ItemViewButtons(m_ui->listView_OpenedDocuments, this); - auto actionCloseDoc = this->getCommandAction("close-doc"); - listViewBtns->addButton(1, actionCloseDoc->icon(), actionCloseDoc->toolTip()); - listViewBtns->setButtonDetection(1, -1, QVariant()); - listViewBtns->setButtonDisplayColumn(1, 0); - listViewBtns->setButtonDisplayModes(1, ItemViewButtons::DisplayOnDetection); - listViewBtns->setButtonItemSide(1, ItemViewButtons::ItemRightSide); - const int iconSize = this->style()->pixelMetric(QStyle::PM_ListViewIconSize); - listViewBtns->setButtonIconSize(1, QSize(iconSize * 0.66, iconSize * 0.66)); - listViewBtns->installDefaultItemDelegate(); - QObject::connect(listViewBtns, &ItemViewButtons::buttonClicked, this, [=](int btnId, QModelIndex index) { - if (btnId == 1) { - auto widgetDoc = this->widgetGuiDocument(index.row()); - if (widgetDoc) - FileCommandTools::closeDocument(m_appContext, widgetDoc->documentIdentifier()); - } - }); - } + guiApp->signalGuiDocumentErased.connectSlot(&MainWindow::onGuiDocumentErased, this); new DialogTaskManager(&m_taskMgr, this); - // BEWARE MainWindow::onGuiDocumentAdded() must be called before - // MainWindow::onCurrentDocumentIndexChanged() - auto guiDocModel = new GuiDocumentListModel(guiApp, this); - m_ui->combo_GuiDocuments->setModel(guiDocModel); - m_ui->listView_OpenedDocuments->setModel(guiDocModel); - - // Finalize setup - m_ui->widget_LeftHeader->installEventFilter(this); - m_ui->widget_ControlGuiDocuments->installEventFilter(this); - m_ui->stack_GuiDocuments->installEventFilter(this); - this->onLeftContentsPageChanged(m_ui->stack_LeftContents->currentIndex()); this->updateControlsActivation(); - m_ui->widget_MouseCoords->hide(); - - this->onCurrentDocumentIndexChanged(-1); } MainWindow::~MainWindow() { + // Force deletion of Command objects as some of them are event filters of MainWindow widgets + m_cmdContainer.clear(); delete m_ui; } -bool MainWindow::eventFilter(QObject* watched, QEvent* event) -{ - auto fnSizeBtn = [](const QWidget* container, const QWidget* widgetHeightRef) { - const int btnSideLen = widgetHeightRef->frameGeometry().height(); - const QList listBtn = container->findChildren(); - for (QAbstractButton* btn : listBtn) - btn->setFixedSize(btnSideLen, btnSideLen); - }; - const QEvent::Type eventType = event->type(); - if (watched == m_ui->widget_ControlGuiDocuments && eventType == QEvent::Show) { - fnSizeBtn(m_ui->widget_ControlGuiDocuments, m_ui->combo_GuiDocuments); - return true; - } - - if (watched == m_ui->widget_LeftHeader && eventType == QEvent::Show) { - fnSizeBtn(m_ui->widget_LeftHeader, m_ui->combo_LeftContents); - return true; - } - - if (watched == m_ui->stack_GuiDocuments) { - if (eventType == QEvent::Enter || eventType == QEvent::Leave) { - m_ui->widget_MouseCoords->setHidden(eventType == QEvent::Leave); - return true; - } - } - - return false; -} - void MainWindow::showEvent(QShowEvent* event) { QMainWindow::showEvent(event); @@ -207,158 +84,122 @@ void MainWindow::showEvent(QShowEvent* event) #endif } +void MainWindow::addPage(IAppContext::Page page, IWidgetMainPage* pageWidget) +{ + assert(m_mapWidgetPage.find(page) == m_mapWidgetPage.cend()); + assert(m_ui->stack_Main->indexOf(pageWidget) == -1); + m_mapWidgetPage.insert({ page, pageWidget }); + m_ui->stack_Main->addWidget(pageWidget); + QObject::connect( + pageWidget, &IWidgetMainPage::updateGlobalControlsActivationRequired, + this, &MainWindow::updateControlsActivation + ); +} + void MainWindow::createCommands() { // "File" commands - this->addCommand("new-doc"); - this->addCommand("open-docs"); - this->addCommand("recent-files", m_ui->menu_File); - this->addCommand("import"); - this->addCommand("export"); - this->addCommand("close-doc"); - this->addCommand("close-all-docs"); - this->addCommand("close-all-docs-except-current"); - this->addCommand("quit"); + this->addCommand(); + this->addCommand(); + this->addCommand(m_ui->menu_File); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); // "Display" commands - this->addCommand("change-projection"); - this->addCommand("change-display-mode", m_ui->menu_Display); - this->addCommand("toggle-origin-trihedron"); - this->addCommand("toggle-performance-stats"); - this->addCommand("current-doc-zoom-in"); - this->addCommand("current-doc-zoom-out"); - this->addCommand("current-doc-turn-view-ccw"); - this->addCommand("current-doc-turn-view-cw"); + this->addCommand(); + this->addCommand(m_ui->menu_Display); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); // "Tools" commands - this->addCommand("save-view-image"); - this->addCommand("inspect-xde"); - this->addCommand("edit-options"); + this->addCommand(); + this->addCommand(); + this->addCommand(); // "Window" commands - this->addCommand("toggle-left-sidebar"); - this->addCommand("toggle-fullscreen"); - this->addCommand("previous-doc"); - this->addCommand("next-doc"); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); + this->addCommand(); // "Help" commands - this->addCommand("report-bug"); - this->addCommand("about"); + this->addCommand(); + this->addCommand(); + this->addCommand(); } void MainWindow::createMenus() { - // Helper function to retrieve the QAction associated to the name of a command - auto fnGetAction = [=](std::string_view commandName) { - return this->getCommandAction(commandName); + // Helper function to add in 'menu' the QAction associated to 'commandName' + auto fnAddAction = [=](QMenu* menu, std::string_view commandName) { + menu->addAction(m_cmdContainer.findCommandAction(commandName)); }; + // TODO Create menu bar programmatically(not hard-code in .ui file) + { // File auto menu = m_ui->menu_File; - menu->addAction(fnGetAction("new-doc")); - menu->addAction(fnGetAction("open-docs")); - menu->addAction(fnGetAction("recent-files")); + fnAddAction(menu, CommandNewDocument::Name); + fnAddAction(menu, CommandOpenDocuments::Name); + fnAddAction(menu, CommandRecentFiles::Name); menu->addSeparator(); - menu->addAction(fnGetAction("import")); - menu->addAction(fnGetAction("export")); + fnAddAction(menu, CommandImportInCurrentDocument::Name); + fnAddAction(menu, CommandExportSelectedApplicationItems::Name); menu->addSeparator(); - menu->addAction(fnGetAction("close-doc")); - menu->addAction(fnGetAction("close-all-docs-except-current")); - menu->addAction(fnGetAction("close-all-docs")); + fnAddAction(menu, CommandCloseCurrentDocument::Name); + fnAddAction(menu, CommandCloseAllDocumentsExceptCurrent::Name); + fnAddAction(menu, CommandCloseAllDocuments::Name); menu->addSeparator(); - menu->addAction(fnGetAction("quit")); + fnAddAction(menu, CommandQuitApplication::Name); } { // Display auto menu = m_ui->menu_Display; - menu->addAction(fnGetAction("change-projection")); - menu->addAction(fnGetAction("change-display-mode")); - menu->addAction(fnGetAction("toggle-origin-trihedron")); - menu->addAction(fnGetAction("toggle-performance-stats")); + fnAddAction(menu, CommandChangeProjection::Name); + fnAddAction(menu, CommandChangeDisplayMode::Name); + fnAddAction(menu, CommandToggleOriginTrihedron::Name); + fnAddAction(menu, CommandTogglePerformanceStats::Name); menu->addSeparator(); - menu->addAction(fnGetAction("current-doc-zoom-in")); - menu->addAction(fnGetAction("current-doc-zoom-out")); - menu->addAction(fnGetAction("current-doc-turn-view-ccw")); - menu->addAction(fnGetAction("current-doc-turn-view-cw")); + fnAddAction(menu, CommandZoomInCurrentDocument::Name); + fnAddAction(menu, CommandZoomOutCurrentDocument::Name); + fnAddAction(menu, CommandTurnViewCounterClockWise::Name); + fnAddAction(menu, CommandTurnViewClockWise::Name); } { // Tools auto menu = m_ui->menu_Tools; - menu->addAction(fnGetAction("save-view-image")); - menu->addAction(fnGetAction("inspect-xde")); + fnAddAction(menu, CommandSaveViewImage::Name); + fnAddAction(menu, CommandInspectXde::Name); menu->addSeparator(); - menu->addAction(fnGetAction("edit-options")); + fnAddAction(menu, CommandEditOptions::Name); } { // Window auto menu = m_ui->menu_Window; - menu->addAction(fnGetAction("toggle-left-sidebar")); - menu->addAction(fnGetAction("toggle-fullscreen")); + fnAddAction(menu, CommandLeftSidebarWidgetToggle::Name); + fnAddAction(menu, CommandMainWidgetToggleFullscreen::Name); menu->addSeparator(); - menu->addAction(fnGetAction("previous-doc")); - menu->addAction(fnGetAction("next-doc")); + fnAddAction(menu, CommandSwitchMainWidgetMode::Name); + fnAddAction(menu, CommandPreviousDocument::Name); + fnAddAction(menu, CommandNextDocument::Name); } { // Help auto menu = m_ui->menu_Help; - menu->addAction(fnGetAction("report-bug")); + fnAddAction(menu, CommandReportBug::Name); + fnAddAction(menu, CommandSystemInformation::Name); menu->addSeparator(); - menu->addAction(fnGetAction("about")); - } -} - -void MainWindow::onApplicationItemSelectionChanged() -{ - WidgetModelTree* uiModelTree = m_ui->widget_ModelTree; - WidgetPropertiesEditor* uiProps = m_ui->widget_Properties; - - uiProps->clear(); - Span spanAppItem = m_guiApp->selectionModel()->selectedItems(); - if (spanAppItem.size() == 1) { - const ApplicationItem& appItem = spanAppItem.front(); - if (appItem.isDocument()) { - auto dataProps = new DocumentPropertyGroup(appItem.document()); - uiProps->editProperties(dataProps, uiProps->addGroup(tr("Data"))); - m_ptrCurrentNodeDataProperties.reset(dataProps); - } - else if (appItem.isDocumentTreeNode()) { - const DocumentTreeNode& docTreeNode = appItem.documentTreeNode(); - auto dataProps = AppModule::get()->properties(docTreeNode); - if (dataProps) { - uiProps->editProperties(dataProps.get(), uiProps->addGroup(tr("Data"))); - dataProps->signalPropertyChanged.connectSlot([=]{ uiModelTree->refreshItemText(appItem); }); - m_ptrCurrentNodeDataProperties = std::move(dataProps); - } - - GuiDocument* guiDoc = m_guiApp->findGuiDocument(appItem.document()); - std::vector vecGfxObject; - guiDoc->foreachGraphicsObject(docTreeNode.id(), [&](GraphicsObjectPtr gfxObject) { - vecGfxObject.push_back(std::move(gfxObject)); - }); - auto commonGfxDriver = GraphicsObjectDriver::getCommon(vecGfxObject); - if (commonGfxDriver) { - auto gfxProps = commonGfxDriver->properties(vecGfxObject); - if (gfxProps) { - uiProps->editProperties(gfxProps.get(), uiProps->addGroup(tr("Graphics"))); - gfxProps->signalPropertyChanged.connectSlot([=]{ guiDoc->graphicsScene()->redraw(); }); - m_ptrCurrentNodeGraphicsProperties = std::move(gfxProps); - } - } - } - - auto app = m_guiApp->application(); - if (AppModule::get()->properties()->linkWithDocumentSelector) { - const int index = app->findIndexOfDocument(appItem.document()); - if (index != -1) - this->setCurrentDocumentIndex(index); - } - } - else { - // TODO - uiProps->clear(); + fnAddAction(menu, CommandAbout::Name); } - - this->updateControlsActivation(); } void MainWindow::onOperationFinished(bool ok, const QString &msg) @@ -396,110 +237,34 @@ void MainWindow::onGuiDocumentAdded(GuiDocument* guiDoc) fnConfigureHighlightStyle(gfxScene->drawerHighlight(Prs3d_TypeOfHighlight_LocalSelected).get()); fnConfigureHighlightStyle(gfxScene->drawerHighlight(Prs3d_TypeOfHighlight_Selected).get()); - // Configure 3D view behavior with respect to application settings - auto appModule = AppModule::get(); - auto appProps = appModule->properties(); - auto widget = new WidgetGuiDocument(guiDoc); - guiDoc->setDevicePixelRatio(widget->devicePixelRatioF()); - auto widgetCtrl = widget->controller(); - widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); - widgetCtrl->setNavigationStyle(appProps->navigationStyle); - if (appProps->defaultShowOriginTrihedron) { - guiDoc->toggleOriginTrihedronVisibility(); - gfxScene->redraw(); - } - - appModule->settings()->signalChanged.connectSlot([=](const Property* setting) { - if (setting == &appProps->instantZoomFactor) - widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); - else if (setting == &appProps->navigationStyle) - widgetCtrl->setNavigationStyle(appProps->navigationStyle); - }); - - // React to mouse move in 3D view: - // * update highlighting - // * compute and display 3D mouse coordinates(by silent picking) - widgetCtrl->signalMouseMoved.connectSlot([=](int xPos, int yPos) { - const double dpRatio = this->devicePixelRatioF(); - gfxScene->highlightAt(xPos * dpRatio, yPos * dpRatio, guiDoc->v3dView()); - widget->view()->redraw(); - auto selector = gfxScene->mainSelector(); - selector->Pick(xPos, yPos, guiDoc->v3dView()); - const gp_Pnt pos3d = - selector->NbPicked() > 0 ? - selector->PickedPoint(1) : - GraphicsUtils::V3dView_to3dPosition(guiDoc->v3dView(), xPos, yPos); - m_ui->label_ValuePosX->setText(QString::number(pos3d.X(), 'f', 3)); - m_ui->label_ValuePosY->setText(QString::number(pos3d.Y(), 'f', 3)); - m_ui->label_ValuePosZ->setText(QString::number(pos3d.Z(), 'f', 3)); - }); - - m_ui->stack_GuiDocuments->addWidget(widget); + this->updateCurrentPage(); this->updateControlsActivation(); - const int newDocIndex = m_guiApp->application()->documentCount() - 1; - QTimer::singleShot(0, this, [=]{ this->setCurrentDocumentIndex(newDocIndex); }); -} - -void MainWindow::onWidgetFileSystemLocationActivated(const QFileInfo& loc) -{ - this->openDocument(filepathFrom(loc)); } -void MainWindow::onLeftContentsPageChanged(int pageId) +void MainWindow::onGuiDocumentErased(GuiDocument* /*guiDoc*/) { - m_ui->stack_LeftContents->setCurrentIndex(pageId); - QWidget* placeHolder = this->recreateLeftHeaderPlaceHolder(); - if (m_ui->stack_LeftContents->currentWidget() == m_ui->page_ModelTree && placeHolder) { - const int btnSideLen = m_ui->combo_LeftContents->frameGeometry().height(); - auto btnSettings = new QToolButton(placeHolder); - btnSettings->setAutoRaise(true); - btnSettings->setFixedSize(btnSideLen, btnSideLen); - btnSettings->setIcon(mayoTheme()->icon(Theme::Icon::Gear)); - btnSettings->setToolTip(tr("Options")); - placeHolder->layout()->addWidget(btnSettings); - btnSettings->setMenu(this->createMenuModelTreeSettings()); - btnSettings->setPopupMode(QToolButton::InstantPopup); - } - else { - delete placeHolder; - } -} - -void MainWindow::onCurrentDocumentIndexChanged(int idx) -{ - m_ui->stack_GuiDocuments->setCurrentIndex(idx); - QAbstractItemView* view = m_ui->listView_OpenedDocuments; - view->setCurrentIndex(view->model()->index(idx, 0)); - + this->updateCurrentPage(); this->updateControlsActivation(); - - const DocumentPtr docPtr = m_guiApp->application()->findDocumentByIndex(idx); - const FilePath docFilePath = docPtr ? docPtr->filePath() : FilePath(); - m_ui->widget_FileSystem->setLocation(filepathTo(docFilePath)); } -void MainWindow::onMessage(Messenger::MessageType msgType, const QString& text) +void MainWindow::onMessage(MessageType msgType, const QString& text) { switch (msgType) { - case Messenger::MessageType::Trace: + case MessageType::Trace: + qDebug() << text; break; - case Messenger::MessageType::Info: + case MessageType::Info: WidgetMessageIndicator::showInfo(text, this); break; - case Messenger::MessageType::Warning: + case MessageType::Warning: QtWidgetsUtils::asyncMsgBoxWarning(this, tr("Warning"), text); break; - case Messenger::MessageType::Error: + case MessageType::Error: QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), text); break; } } -void MainWindow::openDocument(const FilePath& fp) -{ - FileCommandTools::openDocument(m_appContext, fp); -} - void MainWindow::openDocumentsFromList(Span listFilePath) { FileCommandTools::openDocumentsFromList(m_appContext, listFilePath); @@ -507,99 +272,34 @@ void MainWindow::openDocumentsFromList(Span listFilePath) void MainWindow::updateControlsActivation() { - const QWidget* currMainPage = m_ui->stack_Main->currentWidget(); - const int appDocumentsCount = m_guiApp->application()->documentCount(); - const bool appDocumentsEmpty = appDocumentsCount == 0; - QWidget* newMainPage = appDocumentsEmpty ? m_ui->page_MainHome : m_ui->page_MainControl; - if (currMainPage != newMainPage) - m_ui->stack_Main->setCurrentWidget(newMainPage); - - for (auto [name, cmd] : m_mapCommand) { + m_cmdContainer.foreachCommand([](std::string_view, Command* cmd) { cmd->action()->setEnabled(cmd->getEnabledStatus()); - } - - m_ui->combo_GuiDocuments->setEnabled(!appDocumentsEmpty); -} - -int MainWindow::currentDocumentIndex() const -{ - return m_ui->combo_GuiDocuments->currentIndex(); -} - -void MainWindow::setCurrentDocumentIndex(int idx) -{ - m_ui->combo_GuiDocuments->setCurrentIndex(idx); -} - -WidgetGuiDocument* MainWindow::widgetGuiDocument(int idx) const -{ - return qobject_cast(m_ui->stack_GuiDocuments->widget(idx)); -} - -WidgetGuiDocument* MainWindow::currentWidgetGuiDocument() const -{ - return this->widgetGuiDocument(this->currentDocumentIndex()); -} - -QWidget* MainWindow::findLeftHeaderPlaceHolder() const -{ - return m_ui->widget_LeftHeader->findChild( - "LeftHeaderPlaceHolder", Qt::FindDirectChildrenOnly - ); + }); } -QWidget* MainWindow::recreateLeftHeaderPlaceHolder() +void MainWindow::updateCurrentPage() { - QWidget* placeHolder = this->findLeftHeaderPlaceHolder(); - delete placeHolder; - placeHolder = new QWidget(m_ui->widget_LeftHeader); - placeHolder->setObjectName("LeftHeaderPlaceHolder"); - auto layoutPlaceHolder = new QHBoxLayout(placeHolder); - layoutPlaceHolder->setContentsMargins(0, 0, 0, 0); - layoutPlaceHolder->setSpacing(0); - m_ui->Layout_WidgetLeftHeader->insertWidget(2, placeHolder); - return placeHolder; + const IAppContext::Page currentPage = m_appContext->currentPage(); + const bool appDocumentsEmpty = m_guiApp->guiDocuments().empty(); + const auto newPage = appDocumentsEmpty ? IAppContext::Page::Home : IAppContext::Page::Documents; + if (currentPage != newPage) + m_appContext->setCurrentPage(newPage); } -QMenu* MainWindow::createMenuModelTreeSettings() +IWidgetMainPage* MainWindow::widgetMainPage(IAppContext::Page page) const { - auto menu = new QMenu(this->findLeftHeaderPlaceHolder()); - menu->setToolTipsVisible(true); - - // Link with document selector - auto appModule = AppModule::get(); - QAction* action = menu->addAction(to_QString(appModule->properties()->linkWithDocumentSelector.name().tr())); - action->setCheckable(true); - QObject::connect(action, &QAction::triggered, this, [=](bool on) { - appModule->properties()->linkWithDocumentSelector.setValue(on); - }); - - // Model tree user actions - menu->addSeparator(); - const WidgetModelTree_UserActions userActions = m_ui->widget_ModelTree->createUserActions(menu); - for (QAction* usrAction : userActions.items) - menu->addAction(usrAction); - - // Sync before menu show - QObject::connect(menu, &QMenu::aboutToShow, this, [=]{ - action->setChecked(appModule->properties()->linkWithDocumentSelector); - if (userActions.fnSyncItems) - userActions.fnSyncItems(); - }); - - return menu; + auto it = m_mapWidgetPage.find(page); + return it != m_mapWidgetPage.cend() ? it->second : nullptr; } -Command* MainWindow::getCommand(std::string_view name) const +WidgetMainHome* MainWindow::widgetPageHome() const { - auto it = m_mapCommand.find(name); - return it != m_mapCommand.cend() ? it->second : nullptr; + return dynamic_cast(this->widgetMainPage(IAppContext::Page::Home)); } -QAction* MainWindow::getCommandAction(std::string_view name) const +WidgetMainControl* MainWindow::widgetPageDocuments() const { - auto cmd = this->getCommand(name); - return cmd ? cmd->action() : nullptr; + return dynamic_cast(this->widgetMainPage(IAppContext::Page::Documents)); } } // namespace Mayo diff --git a/src/app/mainwindow.h b/src/app/mainwindow.h index 608aa5c7..c82dd1db 100644 --- a/src/app/mainwindow.h +++ b/src/app/mainwindow.h @@ -6,22 +6,21 @@ #pragma once +#include "commands_api.h" #include "../base/filepath.h" #include "../base/messenger.h" -#include "../base/property.h" #include "../base/task_manager.h" #include "../base/text_id.h" #include -#include -class QFileInfo; +#include namespace Mayo { -class Command; class GuiApplication; class GuiDocument; -class IAppContext; -class WidgetGuiDocument; +class IWidgetMainPage; +class WidgetMainControl; +class WidgetMainHome; // Provides the root widget of the application GUI // It creates and owns the various available commands(actions) @@ -32,63 +31,41 @@ class MainWindow : public QMainWindow { MainWindow(GuiApplication* guiApp, QWidget* parent = nullptr); ~MainWindow(); - void openDocument(const FilePath& fp); void openDocumentsFromList(Span listFilePath); - bool eventFilter(QObject* watched, QEvent* event) override; - protected: void showEvent(QShowEvent* event) override; private: + void addPage(IAppContext::Page page, IWidgetMainPage* pageWidget); + void createCommands(); void createMenus(); + template void addCommand(Args... p) { + m_cmdContainer.addNamedCommand(std::forward(p)...); + } - Command* getCommand(std::string_view name) const; - QAction* getCommandAction(std::string_view name) const; - template CmdType* addCommand(std::string_view name, Args... p); - - void onApplicationItemSelectionChanged(); void onOperationFinished(bool ok, const QString& msg); void onGuiDocumentAdded(GuiDocument* guiDoc); - void onWidgetFileSystemLocationActivated(const QFileInfo& loc); - void onLeftContentsPageChanged(int pageId); - void onCurrentDocumentIndexChanged(int idx); - void onMessage(Messenger::MessageType msgType, const QString& text); + void onGuiDocumentErased(GuiDocument* guiDoc); - void updateControlsActivation(); + void onMessage(MessageType msgType, const QString& text); - int currentDocumentIndex() const; - void setCurrentDocumentIndex(int idx); + void updateControlsActivation(); + void updateCurrentPage(); - WidgetGuiDocument* widgetGuiDocument(int idx) const; - WidgetGuiDocument* currentWidgetGuiDocument() const; - QWidget* findLeftHeaderPlaceHolder() const; - QWidget* recreateLeftHeaderPlaceHolder(); - QMenu* createMenuModelTreeSettings(); + IWidgetMainPage* widgetMainPage(IAppContext::Page page) const; + WidgetMainHome* widgetPageHome() const; + WidgetMainControl* widgetPageDocuments() const; friend class AppContext; IAppContext* m_appContext = nullptr; GuiApplication* m_guiApp = nullptr; + CommandContainer m_cmdContainer; TaskManager m_taskMgr; class Ui_MainWindow* m_ui = nullptr; - std::unordered_map m_mapCommand; - std::unique_ptr m_ptrCurrentNodeDataProperties; - std::unique_ptr m_ptrCurrentNodeGraphicsProperties; + std::unordered_map m_mapWidgetPage; }; - - -// -- -// -- Implementation -// -- - -template CmdType* MainWindow::addCommand(std::string_view name, Args... p) -{ - auto cmd = new CmdType(m_appContext, p...); - m_mapCommand.insert({ name, cmd }); - return cmd; -} - } // namespace Mayo diff --git a/src/app/mainwindow.ui b/src/app/mainwindow.ui index d0cf6e20..dc2ab8b7 100644 --- a/src/app/mainwindow.ui +++ b/src/app/mainwindow.ui @@ -33,519 +33,8 @@ - 0 + -1 - - - - 20 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - :/images/appicon_128.png - - - Qt::AlignHCenter|Qt::AlignTop - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 40 - - - - - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Horizontal - - - false - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - 1 - - - 0 - - - 0 - - - 4 - - - 0 - - - - - - 1 - 0 - - - - - 0 - 22 - - - - - Model tree - - - - - Opened documents - - - - - File system - - - - - - - - QFrame::Plain - - - Qt::Vertical - - - - - - - Close Left Side Bar - - - - :/images/themes/classic/left-arrow-cross_16.png:/images/themes/classic/left-arrow-cross_16.png - - - true - - - - - - - - - - 0 - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::Vertical - - - - - 0 - 1 - - - - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Qt::ScrollBarAlwaysOff - - - QAbstractItemView::NoEditTriggers - - - Qt::ElideMiddle - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - - - - - - - - - - - - - 0 - - - 1 - - - 0 - - - 0 - - - 1 - - - - - - 1 - - - 2 - - - 0 - - - 0 - - - 0 - - - - - - :/images/themes/classic/back_32.png:/images/themes/classic/back_32.png - - - - 12 - 12 - - - - true - - - - - - - - :/images/themes/classic/next_32.png:/images/themes/classic/next_32.png - - - - 12 - 12 - - - - true - - - - - - - - 0 - 22 - - - - QComboBox::AdjustToContents - - - - - - - QFrame::Plain - - - Qt::Vertical - - - - - - - - :/images/themes/classic/cross_32.png:/images/themes/classic/cross_32.png - - - - 12 - 12 - - - - true - - - - - - - QFrame::Plain - - - Qt::Vertical - - - - - - - - 1 - 0 - - - - - - - - - 0 - - - 0 - - - 0 - - - 0 - - - - - 0 - - - - - X= - - - - - - - ? - - - - - - - - - 0 - - - - - Y= - - - - - - - ? - - - - - - - - - 0 - - - - - Z= - - - - - - - ? - - - - - - - - - - - - - - - - 2 - 0 - - - - - - - - - - @@ -592,34 +81,6 @@ - - - WidgetModelTree - QWidget -
widget_model_tree.h
- 1 -
- - WidgetFileSystem - QWidget -
widget_file_system.h
- 1 -
- - WidgetPropertiesEditor - QWidget -
widget_properties_editor.h
- 1 -
- - WidgetHomeFiles - QWidget -
widget_home_files.h
- 1 -
-
- - - + diff --git a/src/app/qstring_conv.h b/src/app/qstring_conv.h index 2b7c5277..85ce484a 100644 --- a/src/app/qstring_conv.h +++ b/src/app/qstring_conv.h @@ -38,6 +38,11 @@ template<> struct StringConv { static auto to(const std::string& str) { return QString::fromStdString(str); } }; +// std::wstring -> QString +template<> struct StringConv { + static auto to(const std::wstring& str) { return QString::fromStdWString(str); } +}; + // std::string_view -> QString template<> struct StringConv { static auto to(std::string_view str) { return QString::fromUtf8(str.data(), int(str.size())); } @@ -97,6 +102,11 @@ template<> struct StringConv { } }; +// QString -> std::wstring +template<> struct StringConv { + static auto to(const QString& str) { return str.toStdWString(); } +}; + // QString -> TCollection_ExtendedString template<> struct StringConv { static auto to(const QString& str) { diff --git a/src/app/qtgui_utils.cpp b/src/app/qtgui_utils.cpp index 776e6930..8b3dcb1a 100644 --- a/src/app/qtgui_utils.cpp +++ b/src/app/qtgui_utils.cpp @@ -56,7 +56,8 @@ QColor lerp(const QColor& a, const QColor& b, double t) MathUtils::lerp(a.red(), b.red(), t), MathUtils::lerp(a.green(), b.green(), t), MathUtils::lerp(a.blue(), b.blue(), t), - MathUtils::lerp(a.alpha(), b.alpha(), t)); + MathUtils::lerp(a.alpha(), b.alpha(), t) + ); } QColor linearColorAt(const QGradient& gradient, double t) @@ -130,14 +131,14 @@ int screenPixelWidth(double screenRatio, const QScreen* screen) { screen = !screen ? QGuiApplication::primaryScreen() : screen; const int screenWidth = screen ? screen->geometry().width() : 800; - return std::round(screenWidth * screenRatio); + return qRound(screenWidth * screenRatio); } int screenPixelHeight(double screenRatio, const QScreen* screen) { screen = !screen ? QGuiApplication::primaryScreen() : screen; const int screenHeight = screen ? screen->geometry().height() : 600; - return std::round(screenHeight * screenRatio); + return qRound(screenHeight * screenRatio); } QSize screenPixelSize(double widthRatio, double heightRatio, const QScreen* screen) diff --git a/src/app/qtwidgets_utils.cpp b/src/app/qtwidgets_utils.cpp index b5bb2469..69227a44 100644 --- a/src/app/qtwidgets_utils.cpp +++ b/src/app/qtwidgets_utils.cpp @@ -112,4 +112,9 @@ void QtWidgetsUtils::moveWidgetLeftTo(QWidget* widget, const QWidget* nextTo, in widget->move(nextTo->mapToParent(QPoint(-frameGeom.width() - margin, 0))); } +void QtWidgetsUtils::collapseWidget(QWidget *widget, bool on) +{ + widget->setMaximumHeight(on ? 0 : 16777215/*Qt_defaultMaximumWidth*/); +} + } // namespace Mayo diff --git a/src/app/qtwidgets_utils.h b/src/app/qtwidgets_utils.h index 5296f502..0760943f 100644 --- a/src/app/qtwidgets_utils.h +++ b/src/app/qtwidgets_utils.h @@ -57,6 +57,8 @@ class QtWidgetsUtils { static void moveWidgetRightTo(QWidget* widget, const QWidget* nextTo, int margin); // Move position of 'widget' so it's displayed stuck to the left of 'nextTo' static void moveWidgetLeftTo(QWidget* widget, const QWidget* nextTo, int margin = 0); + + static void collapseWidget(QWidget* widget, bool on); }; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) diff --git a/src/app/theme.cpp b/src/app/theme.cpp index 59e27789..aab70de8 100644 --- a/src/app/theme.cpp +++ b/src/app/theme.cpp @@ -78,6 +78,7 @@ static QString iconFileName(Theme::Icon icn) case Theme::Icon::Export: return "export.svg"; case Theme::Icon::Expand: return "expand.svg"; case Theme::Icon::Cross: return "cross.svg"; + case Theme::Icon::Grid: return "grid.svg"; case Theme::Icon::Link: return "link.svg"; case Theme::Icon::Back: return "back.svg"; case Theme::Icon::Next: return "next.svg"; diff --git a/src/app/theme.h b/src/app/theme.h index fe1fd569..229f1b75 100644 --- a/src/app/theme.h +++ b/src/app/theme.h @@ -45,6 +45,7 @@ class Theme { Export, Expand, Cross, + Grid, Link, Back, Next, diff --git a/src/app/widget_clip_planes.cpp b/src/app/widget_clip_planes.cpp index 563e0e68..fd2387e7 100644 --- a/src/app/widget_clip_planes.cpp +++ b/src/app/widget_clip_planes.cpp @@ -11,6 +11,7 @@ #include "../base/math_utils.h" #include "../base/settings.h" #include "../base/tkernel_utils.h" +#include "../graphics/graphics_texture2d.h" #include "../graphics/graphics_utils.h" #include "app_module.h" #include "ui_widget_clip_planes.h" @@ -19,7 +20,6 @@ #include #include #include -#include #include #include @@ -205,7 +205,7 @@ void WidgetClipPlanes::setPlaneOn(const Handle_Graphic3d_ClipPlane& plane, bool { plane->SetOn(on); if (!GraphicsUtils::V3dView_hasClipPlane(m_view.v3dView(), plane)) - m_view.v3dView()->AddClipPlane(plane); + m_view->AddClipPlane(plane); } void WidgetClipPlanes::setPlaneRange(ClipPlaneData* data, const Range& range) @@ -244,7 +244,7 @@ void WidgetClipPlanes::createPlaneCappingTexture() auto fileContentsData = reinterpret_cast(fileContents.constData()); Handle_Image_AlienPixMap imageCapping = new Image_AlienPixMap; imageCapping->Load(fileContentsData, fileContents.size(), filenameUtf8.constData()); - m_textureCapping = new Graphic3d_Texture2Dmanual(imageCapping); + m_textureCapping = new GraphicsTexture2D(imageCapping); m_textureCapping->EnableModulate(); m_textureCapping->EnableRepeat(); m_textureCapping->GetParams()->SetScale(Graphic3d_Vec2(0.05f, -0.05f)); diff --git a/src/app/widget_clip_planes.h b/src/app/widget_clip_planes.h index 9375b667..13b15625 100644 --- a/src/app/widget_clip_planes.h +++ b/src/app/widget_clip_planes.h @@ -20,6 +20,7 @@ class QDoubleSpinBox; namespace Mayo { +// Widget panel dedicated to clip planes in 3D view class WidgetClipPlanes : public QWidget { Q_OBJECT public: diff --git a/src/app/widget_explode_assembly.h b/src/app/widget_explode_assembly.h index c4f0af40..9ff605ef 100644 --- a/src/app/widget_explode_assembly.h +++ b/src/app/widget_explode_assembly.h @@ -12,6 +12,7 @@ namespace Mayo { class GuiDocument; +// Widget panel dedicated to exploding of assemblies within a GuiDocument object class WidgetExplodeAssembly : public QWidget { Q_OBJECT public: diff --git a/src/app/widget_grid.cpp b/src/app/widget_grid.cpp new file mode 100644 index 00000000..3c521680 --- /dev/null +++ b/src/app/widget_grid.cpp @@ -0,0 +1,368 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "widget_grid.h" +#include "ui_widget_grid.h" +#include "property_editor_factory.h" +#include "qtgui_utils.h" +#include "qtwidgets_utils.h" +#include "../base/unit_system.h" +#include "../graphics/graphics_utils.h" + +#include +#include +#include +#include +#include + +namespace Mayo { + +namespace { + +QPixmap colorSquarePixmap(const Quantity_Color& color) { + return IPropertyEditorFactory::colorSquarePixmap(QtGuiUtils::toQColor(color)); +} + +QPixmap colorSquarePixmap(const QColor& color) { + return IPropertyEditorFactory::colorSquarePixmap(color); +} + +} // namespace + +WidgetGrid::WidgetGrid(GraphicsViewPtr viewPtr, QWidget* parent) + : QWidget(parent), + m_ui(new Ui_WidgetGrid), + m_viewPtr(viewPtr) +{ + const Handle_V3d_Viewer& viewer = viewPtr->Viewer(); + + // Intial configuration + m_ui->setupUi(this); + + constexpr double maxFloat64 = std::numeric_limits::max(); + const double valueEditorMaxWidth = m_ui->edit_RectSizeX->fontMetrics().averageCharWidth() * 15; + for (auto editor : this->findChildren()) { + editor->setRange(-maxFloat64, maxFloat64); + editor->setMaximumWidth(valueEditorMaxWidth); + } + + m_ui->edit_RectRotation->setRange(-180, 180); + m_ui->edit_CircRotation->setRange(-180, 180); + m_ui->edit_RectStepX->setMinimum(0.); + m_ui->edit_RectStepY->setMinimum(0.); + m_ui->edit_RectSizeX->setMinimum(0.); + m_ui->edit_RectSizeY->setMinimum(0.); + m_ui->edit_CircRadiusStep->setMinimum(0.); + m_ui->edit_CircRadius->setMinimum(0.); + + QtWidgetsUtils::collapseWidget(m_ui->widget_Config, true); + QtWidgetsUtils::collapseWidget(m_ui->widget_Graphics, true); + + // Install grid visibility + m_ui->check_ShowGrid->setChecked(GraphicsUtils::V3dViewer_isGridActive(viewer)); + + // Install grid type + switch (viewer->GridType()) { + case Aspect_GT_Rectangular: + m_ui->combo_Type->setCurrentIndex(0); + m_ui->stack_Config->setCurrentWidget(m_ui->page_Rectangular); + break; + case Aspect_GT_Circular: + m_ui->combo_Type->setCurrentIndex(1); + m_ui->stack_Config->setCurrentWidget(m_ui->page_Circular); + break; + } // endswitch + + // Install rectangular grid params + { + double xOrigin, yOrigin; + double xStep, yStep; + double rotAngle; + viewer->RectangularGridValues(xOrigin, yOrigin, xStep, yStep, rotAngle); + m_ui->edit_RectOriginX->setValue(xOrigin); + m_ui->edit_RectOriginY->setValue(yOrigin); + m_ui->edit_RectStepX->setValue(xStep); + m_ui->edit_RectStepY->setValue(yStep); + m_ui->edit_RectRotation->setValue(UnitSystem::degrees(rotAngle * Quantity_Radian)); + + double xSize, ySize; + double offset; + viewer->RectangularGridGraphicValues(xSize, ySize, offset); + m_ui->edit_RectSizeX->setValue(xSize); + m_ui->edit_RectSizeY->setValue(ySize); + m_ui->edit_RectOffset->setValue(offset); + } + + // Install circular grid params + { + double xOrigin, yOrigin; + double radiusStep; + int divisionCount; + double rotAngle; + viewer->CircularGridValues(xOrigin, yOrigin, radiusStep, divisionCount, rotAngle); + m_ui->edit_CircOriginX->setValue(xOrigin); + m_ui->edit_CircOriginY->setValue(yOrigin); + m_ui->edit_CircRadiusStep->setValue(radiusStep); + m_ui->edit_CircDivision->setValue(divisionCount); + m_ui->edit_CircRotation->setValue(UnitSystem::degrees(rotAngle * Quantity_Radian)); + + double radius; + double offset; + viewer->CircularGridGraphicValues(radius, offset); + m_ui->edit_CircRadius->setValue(radius); + m_ui->edit_CircOffset->setValue(offset); + } + + // Install grid privileged plane + const gp_Ax2 plane = viewer->PrivilegedPlane().Ax2(); + if (plane.IsCoplanar(gp::XOY(), Precision::Confusion(), Precision::Angular())) + m_ui->combo_Plane->setCurrentIndex(0); + else if (plane.IsCoplanar(gp::ZOX(), Precision::Confusion(), Precision::Angular())) + m_ui->combo_Plane->setCurrentIndex(1); + else if (plane.IsCoplanar(gp::YOZ(), Precision::Confusion(), Precision::Angular())) + m_ui->combo_Plane->setCurrentIndex(2); + else + m_ui->combo_Plane->setCurrentIndex(3); + + // Install grid draw mode + Handle_Aspect_Grid gridAspect = GraphicsUtils::V3dViewer_grid(viewer); + if (gridAspect) { + if (gridAspect->DrawMode() == Aspect_GDM_Lines) + m_ui->combo_DrawMode->setCurrentIndex(0); + else if (gridAspect->DrawMode() == Aspect_GDM_Points) + m_ui->combo_DrawMode->setCurrentIndex(1); + } + + // Install grid draw colors + auto gridColors = GraphicsUtils::V3dViewer_gridColors(viewer); + m_ui->btn_Color->setIcon(colorSquarePixmap(gridColors.base)); + m_ui->btn_ColorTenth->setIcon(colorSquarePixmap(gridColors.tenth)); + m_gridColorTenth = gridColors.tenth; + + // Install widgets enable status + m_ui->combo_Plane->setEnabled(GraphicsUtils::V3dViewer_isGridActive(viewer)); + m_ui->widget_Main->setEnabled(GraphicsUtils::V3dViewer_isGridActive(viewer)); + auto planeComboModel = static_cast(m_ui->combo_Plane->model()); + if (planeComboModel) + planeComboModel->item(3)->setFlags(Qt::NoItemFlags); // Custom plane + else + qWarning() << Q_FUNC_INFO << "QComboBox model isn't of type QStandardItemModel"; + + // Signal/slot connections + auto sigComboBoxActivated_int = qOverload(&QComboBox::activated); + QObject::connect( + m_ui->check_ShowGrid, &QCheckBox::clicked, this, &WidgetGrid::activateGrid + ); + QObject::connect(m_ui->combo_Type, sigComboBoxActivated_int, this, [=](int typeIndex) { + m_ui->stack_Config->setCurrentIndex(typeIndex); + auto gridColors = GraphicsUtils::V3dViewer_gridColors(viewer); + viewer->ActivateGrid( + toGridType(m_ui->combo_Type->currentIndex()), + toGridDrawMode(m_ui->combo_DrawMode->currentIndex()) + ); + GraphicsUtils::V3dViewer_setGridColors(viewer, gridColors); + m_viewPtr.redraw(); + }); + QObject::connect(m_ui->combo_Plane, sigComboBoxActivated_int, this, [=](int planeIndex) { + viewer->SetPrivilegedPlane(toPlaneAxis(planeIndex)); + m_viewPtr.redraw(); + }); + QObject::connect(m_ui->combo_DrawMode, sigComboBoxActivated_int, this, [=](int modeIndex) { + GraphicsUtils::V3dViewer_grid(viewer)->SetDrawMode(toGridDrawMode(modeIndex)); + m_viewPtr.redraw(); + }); + QObject::connect(m_ui->btn_Config, &QToolButton::clicked, this, [=](bool on) { + QtWidgetsUtils::collapseWidget(m_ui->widget_Config, !on); + if (on) + m_ui->label_Type->setMinimumWidth(m_ui->label_RectOrigin->width()); + + m_ui->btn_Config->setArrowType(on ? Qt::DownArrow : Qt::RightArrow); + emit this->sizeAdjustmentRequested(); + }); + QObject::connect(m_ui->btn_Graphics, &QToolButton::clicked, this, [=](bool on) { + QtWidgetsUtils::collapseWidget(m_ui->widget_Graphics, !on); + m_ui->btn_Graphics->setArrowType(on ? Qt::DownArrow : Qt::RightArrow); + emit this->sizeAdjustmentRequested(); + }); + QObject::connect( + m_ui->btn_Color, &QToolButton::clicked, this, [=]{ this->chooseGridColor(GridColorType::Base); } + ); + QObject::connect( + m_ui->btn_ColorTenth, &QToolButton::clicked, this, [=]{ this->chooseGridColor(GridColorType::Tenth); } + ); + QObject::connect( + m_ui->check_ColorTenth, &QAbstractButton::toggled, this, &WidgetGrid::enableGridColorTenth + ); + + auto sigGridParamChanged_double = qOverload(&QDoubleSpinBox::valueChanged); + auto sigGridParamChanged_int = qOverload(&QSpinBox::valueChanged); + QObject::connect(m_ui->edit_RectOriginX, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_RectOriginY, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_RectStepX, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_RectStepY, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_RectRotation, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_RectSizeX, sigGridParamChanged_double, this, &WidgetGrid::applyGridGraphicsParams); + QObject::connect(m_ui->edit_RectSizeY, sigGridParamChanged_double, this, &WidgetGrid::applyGridGraphicsParams); + QObject::connect(m_ui->edit_RectOffset, sigGridParamChanged_double, this, &WidgetGrid::applyGridGraphicsParams); + + QObject::connect(m_ui->edit_CircOriginX, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_CircOriginY, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_CircRadiusStep, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_CircDivision, sigGridParamChanged_int, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_CircRotation, sigGridParamChanged_double, this, &WidgetGrid::applyGridParams); + QObject::connect(m_ui->edit_CircRadius, sigGridParamChanged_double, this, &WidgetGrid::applyGridGraphicsParams); + QObject::connect(m_ui->edit_CircOffset, sigGridParamChanged_double, this, &WidgetGrid::applyGridGraphicsParams); +} + +WidgetGrid::~WidgetGrid() +{ + delete m_ui; +} + +Aspect_GridType WidgetGrid::toGridType(int comboBoxItemIndex) +{ + return comboBoxItemIndex == 0 ? Aspect_GT_Rectangular : Aspect_GT_Circular; +} + +Aspect_GridDrawMode WidgetGrid::toGridDrawMode(int comboBoxItemIndex) +{ + switch (comboBoxItemIndex) { + case 0: return Aspect_GDM_Lines; + case 1: return Aspect_GDM_Points; + default: return Aspect_GDM_None; + } +} + +const gp_Ax2& WidgetGrid::toPlaneAxis(int comboBoxItemIndex) +{ + switch (comboBoxItemIndex) { + case 0: return gp::XOY(); + case 1: return gp::ZOX(); + case 2: return gp::YOZ(); + default: return gp::XOY(); + } +} + +void WidgetGrid::activateGrid(bool on) +{ + const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + if (on) { + viewer->ActivateGrid( + toGridType(m_ui->combo_Type->currentIndex()), + toGridDrawMode(m_ui->combo_DrawMode->currentIndex()) + ); + } + else { + viewer->DeactivateGrid(); + } + + m_viewPtr.redraw(); + m_ui->combo_Plane->setEnabled(on); + m_ui->widget_Main->setEnabled(on); +} + +void WidgetGrid::applyGridParams() +{ + auto fnCorrectedGridStep = [](double gridStep) { + return !qFuzzyIsNull(gridStep) ? gridStep : 0.01; + }; + const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + auto gridType = toGridType(m_ui->combo_Type->currentIndex()); + if (gridType == Aspect_GT_Rectangular) { + viewer->SetRectangularGridValues( + m_ui->edit_RectOriginX->value(), + m_ui->edit_RectOriginY->value(), + fnCorrectedGridStep(m_ui->edit_RectStepX->value()), + fnCorrectedGridStep(m_ui->edit_RectStepY->value()), + UnitSystem::radians(m_ui->edit_RectRotation->value() * Quantity_Degree) + ); + } + else if (gridType == Aspect_GT_Circular) { + viewer->SetCircularGridValues( + m_ui->edit_CircOriginX->value(), + m_ui->edit_CircOriginY->value(), + fnCorrectedGridStep(m_ui->edit_CircRadiusStep->value()), + m_ui->edit_CircDivision->value(), + UnitSystem::radians(m_ui->edit_CircRotation->value() * Quantity_Degree) + ); + } + + m_viewPtr.redraw(); +} + +void WidgetGrid::applyGridGraphicsParams() +{ + const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + auto gridType = toGridType(m_ui->combo_Type->currentIndex()); + if (gridType == Aspect_GT_Rectangular) { + viewer->SetRectangularGridGraphicValues( + m_ui->edit_RectSizeX->value(), + m_ui->edit_RectSizeY->value(), + m_ui->edit_RectOffset->value() + ); + } + else if (gridType == Aspect_GT_Circular) { + viewer->SetCircularGridGraphicValues( + m_ui->edit_CircRadius->value(), + m_ui->edit_RectOffset->value() + ); + } + + m_viewPtr.redraw(); +} + +void WidgetGrid::chooseGridColor(GridColorType colorType) +{ + const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + auto gridColors = GraphicsUtils::V3dViewer_gridColors(viewer); + // Helper function to apply some base/tenth grid color + auto fnApplyGridColor = [=](const Quantity_Color& color) { + if (colorType == GridColorType::Base) { + const auto colorTenth = m_ui->check_ColorTenth->isChecked() ? gridColors.tenth : color; + GraphicsUtils::V3dViewer_setGridColors(viewer, { color, colorTenth }); + } + else { + GraphicsUtils::V3dViewer_setGridColors(viewer, { gridColors.base, color }); + } + + m_viewPtr.redraw(); + }; + + // Setup dialog to select a color + auto dlg = new QColorDialog(this); + auto onEntryGridColor = colorType == GridColorType::Base ? gridColors.base : gridColors.tenth; + dlg->setCurrentColor(QtGuiUtils::toQColor(onEntryGridColor)); + + QObject::connect(dlg, &QColorDialog::currentColorChanged, this, [=](const QColor& color) { + fnApplyGridColor(QtGuiUtils::toColor(color)); + }); + QObject::connect(dlg, &QDialog::accepted, this, [=]{ + auto btn = colorType == GridColorType::Base ? m_ui->btn_Color : m_ui->btn_ColorTenth; + btn->setIcon(colorSquarePixmap(dlg->selectedColor())); + if (colorType == GridColorType::Tenth) + m_gridColorTenth = QtGuiUtils::toColor(dlg->selectedColor()); + }); + QObject::connect(dlg, &QDialog::rejected, this, [=]{ + auto btn = colorType == GridColorType::Base ? m_ui->btn_Color : m_ui->btn_ColorTenth; + btn->setIcon(colorSquarePixmap(onEntryGridColor)); + fnApplyGridColor(onEntryGridColor); + }); + + QtWidgetsUtils::asyncDialogExec(dlg); +} + +void WidgetGrid::enableGridColorTenth(bool on) +{ + const Handle_V3d_Viewer& viewer = m_viewPtr->Viewer(); + m_ui->label_ColorTenth->setEnabled(on); + m_ui->btn_ColorTenth->setEnabled(on); + auto gridColors = GraphicsUtils::V3dViewer_gridColors(viewer); + const auto gridColorTenth = on ? m_gridColorTenth : gridColors.base; + GraphicsUtils::V3dViewer_setGridColors(viewer, { gridColors.base, gridColorTenth }); + m_viewPtr.redraw(); +} + +} // namespace Mayo diff --git a/src/app/widget_grid.h b/src/app/widget_grid.h new file mode 100644 index 00000000..d1d0b2e5 --- /dev/null +++ b/src/app/widget_grid.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../graphics/graphics_view_ptr.h" + +#include +class gp_Ax2; + +namespace Mayo { + +// Widget panel dedicated to management of the grid in 3D view +class WidgetGrid : public QWidget { + Q_OBJECT +public: + WidgetGrid(GraphicsViewPtr viewPtr, QWidget* parent = nullptr); + ~WidgetGrid(); + +signals: + void sizeAdjustmentRequested(); + +private: + enum class GridColorType { Base, Tenth }; + + static Aspect_GridType toGridType(int comboBoxItemIndex); + static Aspect_GridDrawMode toGridDrawMode(int comboBoxItemIndex); + static const gp_Ax2& toPlaneAxis(int comboBoxItemIndex); + + void activateGrid(bool on); + void applyGridParams(); + void applyGridGraphicsParams(); + void chooseGridColor(GridColorType colorType); + void enableGridColorTenth(bool on); + + class Ui_WidgetGrid* m_ui = nullptr; + GraphicsViewPtr m_viewPtr; + Quantity_Color m_gridColorTenth; +}; + +} // namespace Mayo diff --git a/src/app/widget_grid.ui b/src/app/widget_grid.ui new file mode 100644 index 00000000..388d0078 --- /dev/null +++ b/src/app/widget_grid.ui @@ -0,0 +1,738 @@ + + + Mayo::WidgetGrid + + + + 0 + 0 + 196 + 336 + + + + Form + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + Show Grid + + + + + + + + 1 + 0 + + + + + Plane: XOY + + + + + Plane: ZOX + + + + + Plane: YOZ + + + + + Plane: Custom + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 2 + + + + + + + Configuration + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + Qt::RightArrow + + + + + + + Qt::Horizontal + + + + 100 + 20 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Type + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + Rectangular + + + + + Circular + + + + + + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + + + + 1 + 0 + + + + Y + + + + + + + Step + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + X + + + 0.010000000000000 + + + + + + + + 1 + 0 + + + + Y + + + 0.010000000000000 + + + + + + + Size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + X + + + + + + + + 1 + 0 + + + + Y + + + + + + + Rotation + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + ° + + + -180.000000000000000 + + + 180.000000000000000 + + + 5.000000000000000 + + + + + + + + 0 + 0 + + + + + + + + Offset + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + + + + + + + + 1 + 0 + + + + X + + + + + + + Origin + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + + + Origin + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + X + + + + + + + + 1 + 0 + + + + Y + + + + + + + Radius + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + + + + + 1 + 0 + + + + + + + + Radius Step + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + + + + Division + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + + + + Rotation + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + ° + + + -180.000000000000000 + + + 180.000000000000000 + + + 5.000000000000000 + + + + + + + Offset + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + + + + + 2 + + + + + + + Graphics + + + true + + + Qt::ToolButtonTextBesideIcon + + + true + + + Qt::RightArrow + + + + + + + Qt::Horizontal + + + + 100 + 20 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 2 + + + + + ... + + + + + + + Tenth Color + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ... + + + + + + + Mode + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + Lines + + + + + Points + + + + + + + + Color + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + true + + + + + + + + 1 + 0 + + + + + + + + + + + + + + + + check_ShowGrid + combo_Plane + btn_Config + combo_Type + edit_RectOriginX + edit_RectOriginY + edit_RectStepX + edit_RectStepY + edit_RectSizeX + edit_RectSizeY + edit_RectRotation + edit_RectOffset + btn_Graphics + combo_DrawMode + btn_Color + btn_ColorTenth + edit_CircOriginX + edit_CircOriginY + edit_CircRadius + edit_CircRadiusStep + edit_CircDivision + edit_CircRotation + edit_CircOffset + + + + diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index 949bb4d4..66bc1c5e 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -15,6 +15,7 @@ #include "theme.h" #include "widget_clip_planes.h" #include "widget_explode_assembly.h" +#include "widget_grid.h" #include "widget_measure.h" #include "widget_occ_view.h" #include "widget_occ_view_controller.h" @@ -145,16 +146,24 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) auto layoutBtns = new QHBoxLayout(widgetBtnsContents); layoutBtns->setSpacing(Internal_widgetMargin + 2); layoutBtns->setContentsMargins(2, 2, 2, 2); + m_btnFitAll = this->createViewBtn(widgetBtnsContents, Theme::Icon::Expand, tr("Fit All")); + + m_btnGrid = this->createViewBtn(widgetBtnsContents, Theme::Icon::Grid, tr("Edit Grid")); + m_btnGrid->setCheckable(true); + m_btnEditClipping = this->createViewBtn(widgetBtnsContents, Theme::Icon::ClipPlane, tr("Edit clip planes")); m_btnEditClipping->setCheckable(true); + m_btnExplode = this->createViewBtn(widgetBtnsContents, Theme::Icon::Multiple, tr("Explode assemblies")); m_btnExplode->setCheckable(true); + m_btnMeasure = this->createViewBtn(widgetBtnsContents, Theme::Icon::Measure, tr("Measure shapes")); m_btnMeasure->setCheckable(true); layoutBtns->addWidget(m_btnFitAll); this->recreateMenuViewProjections(widgetBtnsContents); + layoutBtns->addWidget(m_btnGrid); layoutBtns->addWidget(m_btnEditClipping); layoutBtns->addWidget(m_btnExplode); layoutBtns->addWidget(m_btnMeasure); @@ -168,6 +177,10 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) QObject::connect(m_btnFitAll, &ButtonFlat::clicked, this, [=]{ m_guiDoc->runViewCameraAnimation(&GraphicsUtils::V3dView_fitAll); }); + QObject::connect( + m_btnGrid, &ButtonFlat::checked, + this, &WidgetGuiDocument::toggleWidgetGrid + ); QObject::connect( m_btnEditClipping, &ButtonFlat::checked, this, &WidgetGuiDocument::toggleWidgetClipPlanes @@ -218,6 +231,7 @@ void WidgetGuiDocument::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); this->layoutViewControls(); + this->layoutWidgetPanel(m_widgetGrid); this->layoutWidgetPanel(m_widgetClipPlanes); this->layoutWidgetPanel(m_widgetExplodeAsm); this->layoutWidgetPanel(m_widgetMeasure); @@ -241,6 +255,28 @@ void WidgetGuiDocument::updageWidgetPanelControls(QWidget* panelWidget, ButtonFl } } +void adjustWidgetSize(QWidget* widget) +{ + widget->updateGeometry(); + if (static_cast(widget->parentWidget())) + widget->parentWidget()->adjustSize(); +} + +void WidgetGuiDocument::toggleWidgetGrid(bool on) +{ + if (!m_widgetGrid && on) { + m_widgetGrid = new WidgetGrid(m_guiDoc->graphicsView()); + auto container = this->createWidgetPanelContainer(m_widgetGrid); + QObject::connect( + m_widgetGrid, &WidgetGrid::sizeAdjustmentRequested, + container, [=]{ adjustWidgetSize(m_widgetGrid); }, + Qt::QueuedConnection + ); + } + + this->updageWidgetPanelControls(m_widgetGrid, m_btnGrid); +} + void WidgetGuiDocument::toggleWidgetClipPlanes(bool on) { if (m_widgetClipPlanes) { @@ -273,7 +309,7 @@ void WidgetGuiDocument::toggleWidgetMeasure(bool on) auto container = this->createWidgetPanelContainer(m_widgetMeasure); QObject::connect( m_widgetMeasure, &WidgetMeasure::sizeAdjustmentRequested, - container, &QWidget::adjustSize, + container, [=]{ adjustWidgetSize(m_widgetMeasure); }, Qt::QueuedConnection ); } @@ -289,7 +325,7 @@ void WidgetGuiDocument::exclusiveButtonCheck(ButtonFlat* btnCheck) if (!btnCheck || !btnCheck->isChecked()) return; - ButtonFlat* arrayToggleBtn[] = { m_btnEditClipping, m_btnExplode, m_btnMeasure }; + ButtonFlat* arrayToggleBtn[] = { m_btnGrid, m_btnEditClipping, m_btnExplode, m_btnMeasure }; for (ButtonFlat* btn : arrayToggleBtn) { assert(btn->isCheckable()); if (btn != btnCheck) diff --git a/src/app/widget_gui_document.h b/src/app/widget_gui_document.h index bdc4abf2..c26456ec 100644 --- a/src/app/widget_gui_document.h +++ b/src/app/widget_gui_document.h @@ -20,6 +20,7 @@ class ButtonFlat; class GuiDocument; class WidgetClipPlanes; class WidgetExplodeAssembly; +class WidgetGrid; class WidgetMeasure; class IWidgetOccView; @@ -43,6 +44,7 @@ class WidgetGuiDocument : public QWidget { QWidget* createWidgetPanelContainer(QWidget* widgetContents); void updageWidgetPanelControls(QWidget* panelWidget, ButtonFlat* btnPanel); + void toggleWidgetGrid(bool on); void toggleWidgetClipPlanes(bool on); void toggleWidgetExplode(bool on); void toggleWidgetMeasure(bool on); @@ -61,10 +63,12 @@ class WidgetGuiDocument : public QWidget { WidgetOccViewController* m_controller = nullptr; WidgetClipPlanes* m_widgetClipPlanes = nullptr; WidgetExplodeAssembly* m_widgetExplodeAsm = nullptr; + WidgetGrid* m_widgetGrid = nullptr; WidgetMeasure* m_widgetMeasure = nullptr; QRect m_rectControls; ButtonFlat* m_btnFitAll = nullptr; + ButtonFlat* m_btnGrid = nullptr; ButtonFlat* m_btnEditClipping = nullptr; ButtonFlat* m_btnExplode = nullptr; ButtonFlat* m_btnMeasure = nullptr; diff --git a/src/app/widget_home_files.h b/src/app/widget_home_files.h index 1f02721f..93cdb222 100644 --- a/src/app/widget_home_files.h +++ b/src/app/widget_home_files.h @@ -20,7 +20,7 @@ class WidgetHomeFiles : public QWidget { signals: void newDocumentRequested(); void openDocumentsRequested(); - void recentFileOpenRequested(const FilePath& fp); + void recentFileOpenRequested(const Mayo::FilePath& fp); protected: void resizeEvent(QResizeEvent* event) override; diff --git a/src/app/widget_main_control.cpp b/src/app/widget_main_control.cpp new file mode 100644 index 00000000..51eca635 --- /dev/null +++ b/src/app/widget_main_control.cpp @@ -0,0 +1,419 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "widget_main_control.h" +#include "ui_widget_main_control.h" + +#include "../base/application.h" +#include "../graphics/graphics_utils.h" +#include "../gui/gui_application.h" + +#include "app_module.h" +#include "commands_api.h" +#include "commands_file.h" +#include "commands_window.h" +#include "document_property_group.h" +#include "filepath_conv.h" +#include "gui_document_list_model.h" +#include "item_view_buttons.h" +#include "qstring_conv.h" +#include "theme.h" +#include "widget_file_system.h" +#include "widget_gui_document.h" +#include "widget_model_tree.h" +#include "widget_occ_view.h" +#include "widget_properties_editor.h" + +#include +#include +#include + +namespace Mayo { + +WidgetMainControl::WidgetMainControl(GuiApplication* guiApp, QWidget* parent) + : IWidgetMainPage(parent), + m_ui(new Ui_WidgetMainControl), + m_guiApp(guiApp) +{ + assert(m_guiApp != nullptr); + + m_ui->setupUi(this); + + m_ui->widget_ModelTree->registerGuiApplication(guiApp); + + m_ui->splitter_Main->setChildrenCollapsible(false); + m_ui->splitter_Main->setStretchFactor(0, 1); + m_ui->splitter_Main->setStretchFactor(1, 3); + + m_ui->splitter_ModelTree->setStretchFactor(0, 1); + m_ui->splitter_ModelTree->setStretchFactor(1, 2); + + m_ui->stack_LeftContents->setCurrentIndex(0); + + m_ui->widget_Properties->setRowHeightFactor(1.4); + m_ui->widget_Properties->clear(); + + mayoTheme()->setupHeaderComboBox(m_ui->combo_LeftContents); + mayoTheme()->setupHeaderComboBox(m_ui->combo_GuiDocuments); + + // "Window" actions and navigation in documents + QObject::connect( + m_ui->combo_GuiDocuments, qOverload(&QComboBox::currentIndexChanged), + this, &WidgetMainControl::onCurrentDocumentIndexChanged + ); + QObject::connect( + m_ui->widget_FileSystem, &WidgetFileSystem::locationActivated, + this, &WidgetMainControl::onWidgetFileSystemLocationActivated + ); + // ... + QObject::connect( + m_ui->combo_LeftContents, qOverload(&QComboBox::currentIndexChanged), + this, &WidgetMainControl::onLeftContentsPageChanged + ); + QObject::connect( + m_ui->listView_OpenedDocuments, &QListView::clicked, + this, [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); } + ); + + guiApp->application()->signalDocumentFilePathChanged.connectSlot([=](const DocumentPtr& doc, const FilePath& fp) { + if (this->currentWidgetGuiDocument()->documentIdentifier() == doc->identifier()) + m_ui->widget_FileSystem->setLocation(filepathTo(fp)); + }); + guiApp->selectionModel()->signalChanged.connectSlot(&WidgetMainControl::onApplicationItemSelectionChanged, this); + guiApp->signalGuiDocumentAdded.connectSlot(&WidgetMainControl::onGuiDocumentAdded, this); + + // Creation of annex objects + m_listViewBtns = new ItemViewButtons(m_ui->listView_OpenedDocuments, this); + m_listViewBtns->installDefaultItemDelegate(); + + // BEWARE MainWindow::onGuiDocumentAdded() must be called before + // MainWindow::onCurrentDocumentIndexChanged() + auto guiDocModel = new GuiDocumentListModel(guiApp, this); + m_ui->combo_GuiDocuments->setModel(guiDocModel); + m_ui->listView_OpenedDocuments->setModel(guiDocModel); + + // Finalize setup + m_ui->widget_LeftHeader->installEventFilter(this); + m_ui->widget_ControlGuiDocuments->installEventFilter(this); + m_ui->stack_GuiDocuments->installEventFilter(this); + this->onLeftContentsPageChanged(m_ui->stack_LeftContents->currentIndex()); + m_ui->widget_MouseCoords->hide(); + + this->onCurrentDocumentIndexChanged(-1); +} + +WidgetMainControl::~WidgetMainControl() +{ + delete m_ui; +} + +void WidgetMainControl::initialize(const CommandContainer* cmdContainer) +{ + assert(cmdContainer != nullptr); + + m_appContext = cmdContainer->appContext(); + auto fnFindAction = [=](std::string_view cmdName) { + QAction* action = cmdContainer->findCommandAction(cmdName); + assert(action != nullptr); + return action; + }; + m_ui->btn_PreviousGuiDocument->setDefaultAction(fnFindAction(CommandPreviousDocument::Name)); + m_ui->btn_NextGuiDocument->setDefaultAction(fnFindAction(CommandNextDocument::Name)); + m_ui->btn_CloseGuiDocument->setDefaultAction(fnFindAction(CommandCloseCurrentDocument::Name)); + m_ui->btn_CloseLeftSideBar->setDefaultAction(fnFindAction(CommandLeftSidebarWidgetToggle::Name)); + + // Opened documents GUI + auto actionCloseDoc = fnFindAction(CommandCloseCurrentDocument::Name); + m_listViewBtns->addButton(1, actionCloseDoc->icon(), actionCloseDoc->toolTip()); + m_listViewBtns->setButtonDetection(1, -1, QVariant()); + m_listViewBtns->setButtonDisplayColumn(1, 0); + m_listViewBtns->setButtonDisplayModes(1, ItemViewButtons::DisplayOnDetection); + m_listViewBtns->setButtonItemSide(1, ItemViewButtons::ItemRightSide); + const int iconSize = this->style()->pixelMetric(QStyle::PM_ListViewIconSize); + m_listViewBtns->setButtonIconSize(1, QSize(iconSize * 0.66, iconSize * 0.66)); + QObject::connect(m_listViewBtns, &ItemViewButtons::buttonClicked, this, [=](int btnId, QModelIndex index) { + if (btnId == 1) { + assert(this->widgetGuiDocument(index.row()) != nullptr); + FileCommandTools::closeDocument( + cmdContainer->appContext(), + this->widgetGuiDocument(index.row())->documentIdentifier() + ); + } + }); +} + +void WidgetMainControl::updatePageControlsActivation() +{ + const int appDocumentsCount = m_guiApp->application()->documentCount(); + const bool appDocumentsEmpty = appDocumentsCount == 0; + m_ui->combo_GuiDocuments->setEnabled(!appDocumentsEmpty); +} + +QWidget* WidgetMainControl::widgetLeftSideBar() const +{ + return m_ui->widget_Left; +} + +bool WidgetMainControl::eventFilter(QObject* watched, QEvent* event) +{ + auto fnSizeBtn = [](const QWidget* container, const QWidget* widgetHeightRef) { + const int btnSideLen = widgetHeightRef->frameGeometry().height(); + const QList listBtn = container->findChildren(); + for (QAbstractButton* btn : listBtn) + btn->setFixedSize(btnSideLen, btnSideLen); + }; + const QEvent::Type eventType = event->type(); + if (watched == m_ui->widget_ControlGuiDocuments && eventType == QEvent::Show) { + fnSizeBtn(m_ui->widget_ControlGuiDocuments, m_ui->combo_GuiDocuments); + return true; + } + + if (watched == m_ui->widget_LeftHeader && eventType == QEvent::Show) { + fnSizeBtn(m_ui->widget_LeftHeader, m_ui->combo_LeftContents); + return true; + } + + if (watched == m_ui->stack_GuiDocuments) { + if (eventType == QEvent::Enter || eventType == QEvent::Leave) { + m_ui->widget_MouseCoords->setHidden(eventType == QEvent::Leave); + return true; + } + } + + return false; +} + +QMenu* WidgetMainControl::createMenuModelTreeSettings() +{ + auto menu = new QMenu(this->findLeftHeaderPlaceHolder()); + menu->setToolTipsVisible(true); + + // Link with document selector + auto appModule = AppModule::get(); + QAction* action = menu->addAction(to_QString(appModule->properties()->linkWithDocumentSelector.name().tr())); + action->setCheckable(true); + QObject::connect(action, &QAction::triggered, this, [=](bool on) { + appModule->properties()->linkWithDocumentSelector.setValue(on); + }); + + // Model tree user actions + menu->addSeparator(); + const WidgetModelTree_UserActions userActions = m_ui->widget_ModelTree->createUserActions(menu); + for (QAction* usrAction : userActions.items) + menu->addAction(usrAction); + + // Sync before menu show + QObject::connect(menu, &QMenu::aboutToShow, this, [=]{ + action->setChecked(appModule->properties()->linkWithDocumentSelector); + if (userActions.fnSyncItems) + userActions.fnSyncItems(); + }); + + return menu; +} + +void WidgetMainControl::onApplicationItemSelectionChanged() +{ + WidgetModelTree* uiModelTree = m_ui->widget_ModelTree; + WidgetPropertiesEditor* uiProps = m_ui->widget_Properties; + + uiProps->clear(); + Span spanAppItem = m_guiApp->selectionModel()->selectedItems(); + if (spanAppItem.size() == 1) { + const ApplicationItem& appItem = spanAppItem.front(); + if (appItem.isDocument()) { + auto dataProps = new DocumentPropertyGroup(appItem.document()); + uiProps->editProperties(dataProps, uiProps->addGroup(tr("Data"))); + m_ptrCurrentNodeDataProperties.reset(dataProps); + } + else if (appItem.isDocumentTreeNode()) { + const DocumentTreeNode& docTreeNode = appItem.documentTreeNode(); + auto dataProps = AppModule::get()->properties(docTreeNode); + if (dataProps) { + uiProps->editProperties(dataProps.get(), uiProps->addGroup(tr("Data"))); + dataProps->signalPropertyChanged.connectSlot([=]{ uiModelTree->refreshItemText(appItem); }); + m_ptrCurrentNodeDataProperties = std::move(dataProps); + } + + GuiDocument* guiDoc = m_guiApp->findGuiDocument(appItem.document()); + std::vector vecGfxObject; + guiDoc->foreachGraphicsObject(docTreeNode.id(), [&](GraphicsObjectPtr gfxObject) { + vecGfxObject.push_back(std::move(gfxObject)); + }); + auto commonGfxDriver = GraphicsObjectDriver::getCommon(vecGfxObject); + if (commonGfxDriver) { + auto gfxProps = commonGfxDriver->properties(vecGfxObject); + if (gfxProps) { + uiProps->editProperties(gfxProps.get(), uiProps->addGroup(tr("Graphics"))); + gfxProps->signalPropertyChanged.connectSlot([=]{ guiDoc->graphicsScene()->redraw(); }); + m_ptrCurrentNodeGraphicsProperties = std::move(gfxProps); + } + } + } + + auto app = m_guiApp->application(); + if (AppModule::get()->properties()->linkWithDocumentSelector) { + const int index = app->findIndexOfDocument(appItem.document()); + if (index != -1) + this->setCurrentDocumentIndex(index); + } + } + else { + // TODO + uiProps->clear(); + } + + emit this->updateGlobalControlsActivationRequired(); +} + + +void WidgetMainControl::onLeftContentsPageChanged(int pageId) +{ + m_ui->stack_LeftContents->setCurrentIndex(pageId); + QWidget* placeHolder = this->recreateLeftHeaderPlaceHolder(); + if (m_ui->stack_LeftContents->currentWidget() == m_ui->page_ModelTree && placeHolder) { + const int btnSideLen = m_ui->combo_LeftContents->frameGeometry().height(); + auto btnSettings = new QToolButton(placeHolder); + btnSettings->setAutoRaise(true); + btnSettings->setFixedSize(btnSideLen, btnSideLen); + btnSettings->setIcon(mayoTheme()->icon(Theme::Icon::Gear)); + btnSettings->setToolTip(tr("Options")); + placeHolder->layout()->addWidget(btnSettings); + btnSettings->setMenu(this->createMenuModelTreeSettings()); + btnSettings->setPopupMode(QToolButton::InstantPopup); + } + else { + delete placeHolder; + } +} + +QWidget* WidgetMainControl::findLeftHeaderPlaceHolder() const +{ + return m_ui->widget_LeftHeader->findChild( + "LeftHeaderPlaceHolder", Qt::FindDirectChildrenOnly + ); +} + +QWidget* WidgetMainControl::recreateLeftHeaderPlaceHolder() +{ + QWidget* placeHolder = this->findLeftHeaderPlaceHolder(); + delete placeHolder; + placeHolder = new QWidget(m_ui->widget_LeftHeader); + placeHolder->setObjectName("LeftHeaderPlaceHolder"); + auto layoutPlaceHolder = new QHBoxLayout(placeHolder); + layoutPlaceHolder->setContentsMargins(0, 0, 0, 0); + layoutPlaceHolder->setSpacing(0); + m_ui->Layout_WidgetLeftHeader->insertWidget(2, placeHolder); + return placeHolder; +} + +WidgetGuiDocument* WidgetMainControl::widgetGuiDocument(int idx) const +{ + assert(idx == -1 || (0 <= idx && idx < m_ui->stack_GuiDocuments->count())); + return qobject_cast(m_ui->stack_GuiDocuments->widget(idx)); +} + +WidgetGuiDocument* WidgetMainControl::currentWidgetGuiDocument() const +{ + return this->widgetGuiDocument(this->currentDocumentIndex()); +} + +int WidgetMainControl::indexOfWidgetGuiDocument(WidgetGuiDocument* widgetDoc) const +{ + return m_ui->stack_GuiDocuments->indexOf(widgetDoc); +} + +void WidgetMainControl::removeWidgetGuiDocument(WidgetGuiDocument* widgetDoc) +{ + if (widgetDoc) { + m_ui->stack_GuiDocuments->removeWidget(widgetDoc); + widgetDoc->deleteLater(); + } +} + +int WidgetMainControl::widgetGuiDocumentCount() const +{ + return m_ui->stack_GuiDocuments->count(); +} + +void WidgetMainControl::onGuiDocumentAdded(GuiDocument* guiDoc) +{ + auto gfxScene = guiDoc->graphicsScene(); + + // Configure 3D view behavior with respect to application settings + auto appModule = AppModule::get(); + auto appProps = appModule->properties(); + auto widget = new WidgetGuiDocument(guiDoc); + guiDoc->setDevicePixelRatio(widget->devicePixelRatioF()); + auto widgetCtrl = widget->controller(); + widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); + widgetCtrl->setNavigationStyle(appProps->navigationStyle); + if (appProps->defaultShowOriginTrihedron) { + guiDoc->toggleOriginTrihedronVisibility(); + gfxScene->redraw(); + } + + appModule->settings()->signalChanged.connectSlot([=](const Property* setting) { + if (setting == &appProps->instantZoomFactor) + widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); + else if (setting == &appProps->navigationStyle) + widgetCtrl->setNavigationStyle(appProps->navigationStyle); + }); + + // React to mouse move in 3D view: + // * update highlighting + // * compute and display 3D mouse coordinates(by silent picking) + widgetCtrl->signalMouseMoved.connectSlot([=](int xPos, int yPos) { + const double dpRatio = this->devicePixelRatioF(); + gfxScene->highlightAt(xPos * dpRatio, yPos * dpRatio, guiDoc->v3dView()); + widget->view()->redraw(); + auto selector = gfxScene->mainSelector(); + selector->Pick(xPos, yPos, guiDoc->v3dView()); + const gp_Pnt pos3d = + selector->NbPicked() > 0 ? + selector->PickedPoint(1) : + GraphicsUtils::V3dView_to3dPosition(guiDoc->v3dView(), xPos, yPos); + m_ui->label_ValuePosX->setText(QString::number(pos3d.X(), 'f', 3)); + m_ui->label_ValuePosY->setText(QString::number(pos3d.Y(), 'f', 3)); + m_ui->label_ValuePosZ->setText(QString::number(pos3d.Z(), 'f', 3)); + }); + + m_ui->stack_GuiDocuments->addWidget(widget); + const int newDocIndex = m_guiApp->application()->documentCount() - 1; + QTimer::singleShot(0, this, [=]{ this->setCurrentDocumentIndex(newDocIndex); }); +} + +int WidgetMainControl::currentDocumentIndex() const +{ + return m_ui->combo_GuiDocuments->currentIndex(); +} + +void WidgetMainControl::setCurrentDocumentIndex(int idx) +{ + m_ui->combo_GuiDocuments->setCurrentIndex(idx); +} + +void WidgetMainControl::onWidgetFileSystemLocationActivated(const QFileInfo& loc) +{ + FileCommandTools::openDocument(m_appContext, filepathFrom(loc)); +} + +void WidgetMainControl::onCurrentDocumentIndexChanged(int idx) +{ + m_ui->stack_GuiDocuments->setCurrentIndex(idx); + QAbstractItemView* view = m_ui->listView_OpenedDocuments; + view->setCurrentIndex(view->model()->index(idx, 0)); + + emit this->updateGlobalControlsActivationRequired(); + + const DocumentPtr docPtr = m_guiApp->application()->findDocumentByIndex(idx); + const FilePath docFilePath = docPtr ? docPtr->filePath() : FilePath(); + m_ui->widget_FileSystem->setLocation(filepathTo(docFilePath)); + + emit this->currentDocumentIndexChanged(idx); +} + +} // namespace Mayo diff --git a/src/app/widget_main_control.h b/src/app/widget_main_control.h new file mode 100644 index 00000000..9c9d5270 --- /dev/null +++ b/src/app/widget_main_control.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/property.h" +#include "iwidget_main_page.h" + +#include + +class QFileInfo; +class QMenu; + +namespace Mayo { + +class IAppContext; +class CommandContainer; +class GuiApplication; +class GuiDocument; +class ItemViewButtons; +class WidgetGuiDocument; + +// Provides a main page to control opened documents in the application +// Comes with the model tree, 3D view associated to each document, ... +class WidgetMainControl : public IWidgetMainPage { + Q_OBJECT +public: + WidgetMainControl(GuiApplication* guiApp, QWidget* parent = nullptr); + ~WidgetMainControl(); + + void initialize(const CommandContainer* cmdContainer) override; + void updatePageControlsActivation() override; + + // Widget at the left side of the app providing access to the model tree, file system, ... + QWidget* widgetLeftSideBar() const; + + int widgetGuiDocumentCount() const; + WidgetGuiDocument* widgetGuiDocument(int idx) const; + WidgetGuiDocument* currentWidgetGuiDocument() const; + int indexOfWidgetGuiDocument(WidgetGuiDocument* widgetDoc) const; + void removeWidgetGuiDocument(WidgetGuiDocument* widgetDoc); + + int currentDocumentIndex() const; + void setCurrentDocumentIndex(int idx); + + bool eventFilter(QObject* watched, QEvent* event) override; + +signals: + void currentDocumentIndexChanged(int docIndex); + +private: + QMenu* createMenuModelTreeSettings(); + + void onApplicationItemSelectionChanged(); + void onLeftContentsPageChanged(int pageId); + void onWidgetFileSystemLocationActivated(const QFileInfo& loc); + + QWidget* findLeftHeaderPlaceHolder() const; + QWidget* recreateLeftHeaderPlaceHolder(); + + void onGuiDocumentAdded(GuiDocument* guiDoc); + + void onCurrentDocumentIndexChanged(int idx); + + class Ui_WidgetMainControl* m_ui = nullptr; + GuiApplication* m_guiApp = nullptr; + IAppContext* m_appContext = nullptr; + ItemViewButtons* m_listViewBtns = nullptr; + std::unique_ptr m_ptrCurrentNodeDataProperties; + std::unique_ptr m_ptrCurrentNodeGraphicsProperties; +}; + +} // namespace Mayo diff --git a/src/app/widget_main_control.ui b/src/app/widget_main_control.ui new file mode 100644 index 00000000..27ac8e57 --- /dev/null +++ b/src/app/widget_main_control.ui @@ -0,0 +1,491 @@ + + + Mayo::WidgetMainControl + + + + 0 + 0 + 947 + 560 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + false + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 1 + + + 0 + + + 0 + + + 4 + + + 0 + + + + + + 1 + 0 + + + + + 0 + 22 + + + + + Model tree + + + + + Opened documents + + + + + File system + + + + + + + + QFrame::Plain + + + Qt::Vertical + + + + + + + Close Left Side Bar + + + + :/images/themes/classic/left-arrow-cross_16.png:/images/themes/classic/left-arrow-cross_16.png + + + true + + + + + + + + + + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Vertical + + + + + 0 + 1 + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::ScrollBarAlwaysOff + + + QAbstractItemView::NoEditTriggers + + + Qt::ElideMiddle + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + 0 + + + 1 + + + 0 + + + 0 + + + 1 + + + + + + 1 + + + 2 + + + 0 + + + 0 + + + 0 + + + + + + :/images/themes/classic/back_32.png:/images/themes/classic/back_32.png + + + + 12 + 12 + + + + true + + + + + + + + :/images/themes/classic/next_32.png:/images/themes/classic/next_32.png + + + + 12 + 12 + + + + true + + + + + + + + 0 + 22 + + + + QComboBox::AdjustToContents + + + + + + + QFrame::Plain + + + Qt::Vertical + + + + + + + + :/images/themes/classic/cross_32.png:/images/themes/classic/cross_32.png + + + + 12 + 12 + + + + true + + + + + + + QFrame::Plain + + + Qt::Vertical + + + + + + + + 1 + 0 + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + + + X= + + + + + + + ? + + + + + + + + + 0 + + + + + Y= + + + + + + + ? + + + + + + + + + 0 + + + + + Z= + + + + + + + ? + + + + + + + + + + + + + + + + 2 + 0 + + + + + + + + + + + + + WidgetModelTree + QWidget +
widget_model_tree.h
+ 1 +
+ + WidgetFileSystem + QWidget +
widget_file_system.h
+ 1 +
+ + WidgetPropertiesEditor + QWidget +
widget_properties_editor.h
+ 1 +
+
+ + +
diff --git a/src/app/widget_main_home.cpp b/src/app/widget_main_home.cpp new file mode 100644 index 00000000..ce67353e --- /dev/null +++ b/src/app/widget_main_home.cpp @@ -0,0 +1,49 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "widget_main_home.h" +#include "ui_widget_main_home.h" + +#include "commands_file.h" + +namespace Mayo { + +WidgetMainHome::WidgetMainHome(QWidget* parent) + : IWidgetMainPage(parent), + m_ui(new Ui::WidgetMainHome) +{ + m_ui->setupUi(this); +} + +WidgetMainHome::~WidgetMainHome() +{ + delete m_ui; +} + +void WidgetMainHome::initialize(const CommandContainer* cmdContainer) +{ + assert(cmdContainer != nullptr); + IAppContext* appContext = cmdContainer->appContext(); + + QObject::connect( + m_ui->widget_HomeFiles, &WidgetHomeFiles::newDocumentRequested, + cmdContainer->findCommand(CommandNewDocument::Name), &Command::execute + ); + QObject::connect( + m_ui->widget_HomeFiles, &WidgetHomeFiles::openDocumentsRequested, + cmdContainer->findCommand(CommandOpenDocuments::Name), &Command::execute + ); + QObject::connect( + m_ui->widget_HomeFiles, &WidgetHomeFiles::recentFileOpenRequested, + this, [=](const FilePath& fp) { FileCommandTools::openDocument(appContext, fp); } + ); +} + +void WidgetMainHome::updatePageControlsActivation() +{ +} + +} // namespace Mayo diff --git a/src/app/widget_main_home.h b/src/app/widget_main_home.h new file mode 100644 index 00000000..1bb226fc --- /dev/null +++ b/src/app/widget_main_home.h @@ -0,0 +1,28 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "iwidget_main_page.h" + +namespace Mayo { + +// Provides the home page of the application +// Gives access to recent files as a 2D grid +class WidgetMainHome : public IWidgetMainPage { + Q_OBJECT +public: + WidgetMainHome(QWidget* parent = nullptr); + ~WidgetMainHome(); + + void initialize(const CommandContainer* cmdContainer) override; + void updatePageControlsActivation() override; + +private: + class Ui_WidgetMainHome* m_ui = nullptr; +}; + +} // namespace Mayo diff --git a/src/app/widget_main_home.ui b/src/app/widget_main_home.ui new file mode 100644 index 00000000..da172417 --- /dev/null +++ b/src/app/widget_main_home.ui @@ -0,0 +1,93 @@ + + + Mayo::WidgetMainHome + + + + 0 + 0 + 770 + 536 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + :/images/appicon_128.png + + + Qt::AlignHCenter|Qt::AlignTop + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + + + + + + + + WidgetHomeFiles + QWidget +
widget_home_files.h
+ 1 +
+
+ + + + +
diff --git a/src/app/widget_measure.cpp b/src/app/widget_measure.cpp index def467db..9a681677 100644 --- a/src/app/widget_measure.cpp +++ b/src/app/widget_measure.cpp @@ -133,9 +133,10 @@ MeasureType WidgetMeasure::toMeasureType(int comboBoxId) case 1: return MeasureType::CircleCenter; case 2: return MeasureType::CircleDiameter; case 3: return MeasureType::MinDistance; - case 4: return MeasureType::Angle; - case 5: return MeasureType::Length; - case 6: return MeasureType::Area; + case 4: return MeasureType::CenterDistance; + case 5: return MeasureType::Angle; + case 6: return MeasureType::Length; + case 7: return MeasureType::Area; } return MeasureType::None; } @@ -329,8 +330,9 @@ void WidgetMeasure::onGraphicsSelectionChanged() } // Display new measure graphics objects + auto measureDisplayConfig = this->currentMeasureDisplayConfig(); for (IMeasureDisplayPtr& measure : vecNewMeasureDisplay) { - measure->update(this->currentMeasureDisplayConfig()); + measure->update(measureDisplayConfig); measure->adaptGraphics(gfxScene->v3dViewer()->Driver()); foreachGraphicsObject(measure, [=](const GraphicsObjectPtr& gfxObject) { gfxObject->SetZLayer(Graphic3d_ZLayerId_Topmost); diff --git a/src/app/widget_measure.h b/src/app/widget_measure.h index c43d5412..f451433d 100644 --- a/src/app/widget_measure.h +++ b/src/app/widget_measure.h @@ -20,6 +20,7 @@ namespace Mayo { class GuiDocument; +// Widget panel dedicated to measurements in 3D view class WidgetMeasure : public QWidget { Q_OBJECT public: diff --git a/src/app/widget_measure.ui b/src/app/widget_measure.ui index 8c06c05f..d5b2559f 100644 --- a/src/app/widget_measure.ui +++ b/src/app/widget_measure.ui @@ -124,6 +124,11 @@ Min Distance
+ + + Center-to-center Distance + + Angle diff --git a/src/app/widget_model_tree.cpp b/src/app/widget_model_tree.cpp index 3089d190..a2939bbe 100644 --- a/src/app/widget_model_tree.cpp +++ b/src/app/widget_model_tree.cpp @@ -143,11 +143,13 @@ WidgetModelTree::WidgetModelTree(QWidget* widget) modelTreeBtns->addButton( idBtnRemove, mayoTheme()->icon(Theme::Icon::Cross), - tr("Remove from document")); + tr("Remove from document") + ); modelTreeBtns->setButtonDetection( idBtnRemove, Internal::TreeItemTypeRole, - QVariant(Internal::TreeItemType_DocumentEntity)); + QVariant(Internal::TreeItemType_DocumentEntity) + ); modelTreeBtns->setButtonDisplayColumn(idBtnRemove, 0); modelTreeBtns->setButtonDisplayModes(idBtnRemove, ItemViewButtons::DisplayOnDetection); modelTreeBtns->setButtonItemSide(idBtnRemove, ItemViewButtons::ItemRightSide); @@ -318,7 +320,8 @@ WidgetModelTreeBuilder* WidgetModelTree::findSupportBuilder(const DocumentPtr& d auto it = std::find_if( std::next(m_vecBuilder.cbegin()), m_vecBuilder.cend(), - [=](const BuilderPtr& builder) { return builder->supportsDocument(doc); }); + [=](const BuilderPtr& builder) { return builder->supportsDocument(doc); } + ); return it != m_vecBuilder.cend() ? it->get() : m_vecBuilder.front().get(); } @@ -328,7 +331,8 @@ WidgetModelTreeBuilder* WidgetModelTree::findSupportBuilder(const DocumentTreeNo auto it = std::find_if( std::next(m_vecBuilder.cbegin()), m_vecBuilder.cend(), - [=](const BuilderPtr& builder) { return builder->supportsDocumentTreeNode(node); }); + [=](const BuilderPtr& builder) { return builder->supportsDocumentTreeNode(node); } + ); return it != m_vecBuilder.cend() ? it->get() : m_vecBuilder.front().get(); } @@ -409,7 +413,8 @@ void WidgetModelTree::connectTreeModelDataChanged(bool on) if (on) { m_connTreeModelDataChanged = QObject::connect( m_ui->treeWidget_Model->model(), &QAbstractItemModel::dataChanged, - this, &WidgetModelTree::onTreeModelDataChanged, Qt::UniqueConnection); + this, &WidgetModelTree::onTreeModelDataChanged, Qt::UniqueConnection + ); } else { QObject::disconnect(m_connTreeModelDataChanged); @@ -422,7 +427,8 @@ void WidgetModelTree::connectTreeWidgetDocumentSelectionChanged(bool on) m_connTreeWidgetDocumentSelectionChanged = QObject::connect( m_ui->treeWidget_Model->selectionModel(), &QItemSelectionModel::selectionChanged, this, &WidgetModelTree::onTreeWidgetDocumentSelectionChanged, - Qt::UniqueConnection); + Qt::UniqueConnection + ); } else { QObject::disconnect(m_connTreeWidgetDocumentSelectionChanged); @@ -430,7 +436,8 @@ void WidgetModelTree::connectTreeWidgetDocumentSelectionChanged(bool on) } void WidgetModelTree::onTreeModelDataChanged( - const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles) + const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles + ) { if (roles.contains(Qt::CheckStateRole) && topLeft == bottomRight) { const QModelIndex& indexItem = topLeft; @@ -454,7 +461,8 @@ void WidgetModelTree::onTreeModelDataChanged( } void WidgetModelTree::onNodesVisibilityChanged( - const GuiDocument* guiDoc, const std::unordered_map& mapNodeId) + const GuiDocument* guiDoc, const std::unordered_map& mapNodeId + ) { QTreeWidgetItem* treeItemDoc = this->findTreeItem(guiDoc->document()); if (!treeItemDoc) diff --git a/src/app/widget_occ_view_impl.cpp b/src/app/widget_occ_view_impl.cpp index d3f33345..d85711ce 100644 --- a/src/app/widget_occ_view_impl.cpp +++ b/src/app/widget_occ_view_impl.cpp @@ -81,7 +81,8 @@ void QOpenGLWidgetOccView_createOpenGlContext(std::functionRenderingContext()); + if (fnCallback) + fnCallback(glCtx->RenderingContext()); } Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView_createCompatibleGraphicsDriver() diff --git a/src/base/application_item.h b/src/base/application_item.h index 0cfec2a3..d5376d44 100644 --- a/src/base/application_item.h +++ b/src/base/application_item.h @@ -11,6 +11,7 @@ namespace Mayo { +// Provides a common item that could be either a Document or some model tree node within a Document class ApplicationItem { public: ApplicationItem() = default; diff --git a/src/base/application_item_selection_model.h b/src/base/application_item_selection_model.h index bfd7863b..b2f48c3e 100644 --- a/src/base/application_item_selection_model.h +++ b/src/base/application_item_selection_model.h @@ -12,6 +12,7 @@ namespace Mayo { +// Keeps track of the items selected in an Application object class ApplicationItemSelectionModel { public: Span selectedItems() const; diff --git a/src/base/brep_utils.cpp b/src/base/brep_utils.cpp index 66e54493..1b2f1197 100644 --- a/src/base/brep_utils.cpp +++ b/src/base/brep_utils.cpp @@ -24,12 +24,27 @@ namespace Mayo { TopoDS_Compound BRepUtils::makeEmptyCompound() { - BRep_Builder builder; + TopoDS_Builder builder; TopoDS_Compound comp; builder.MakeCompound(comp); return comp; } +void BRepUtils::addShape(TopoDS_Shape* ptrTargetShape, const TopoDS_Shape& shape) +{ + TopoDS_Builder builder; + builder.Add(*ptrTargetShape, shape); +} + +TopoDS_Edge BRepUtils::makeEdge(const Handle(Poly_Polygon3D)& polygon) +{ + TopoDS_Edge edge; + BRep_Builder builder; + builder.MakeEdge(edge); + builder.UpdateEdge(edge, polygon); + return edge; +} + TopoDS_Face BRepUtils::makeFace(const Handle(Poly_Triangulation)& mesh) { TopoDS_Face face; @@ -65,6 +80,11 @@ TopoDS_Shape BRepUtils::shapeFromString(const std::string& str) return shape; } +bool Mayo::BRepUtils::isGeometric(const TopoDS_Edge &edge) +{ + return BRep_Tool::IsGeometric(edge); +} + bool BRepUtils::isGeometric(const TopoDS_Face& face) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) diff --git a/src/base/brep_utils.h b/src/base/brep_utils.h index 3a9d6eef..76830e89 100644 --- a/src/base/brep_utils.h +++ b/src/base/brep_utils.h @@ -8,7 +8,9 @@ #include "occ_brep_mesh_parameters.h" +#include #include +#include #include #include #include @@ -23,6 +25,12 @@ struct BRepUtils { // Creates a valid and empty TopoDS_Compound shape static TopoDS_Compound makeEmptyCompound(); + // Adds 'shape' in target shape 'ptrTargetShape' + static void addShape(TopoDS_Shape* ptrTargetShape, const TopoDS_Shape& shape); + + // Creates a non-geometric TopoDS_Edge wrapping 'polygon' + static TopoDS_Edge makeEdge(const Handle(Poly_Polygon3D)& polygon); + // Creates a non-geometric TopoDS_Face wrapping triangulation 'mesh' static TopoDS_Face makeFace(const Handle(Poly_Triangulation)& mesh); @@ -53,6 +61,9 @@ struct BRepUtils { // Deserializes string 'str' obtained from 'shapeToToString()' into a shape object static TopoDS_Shape shapeFromString(const std::string& str); + // Does 'edge' rely on 3D curve of curve on surface? + static bool isGeometric(const TopoDS_Edge& edge); + // Does 'face' rely on a geometric surface? static bool isGeometric(const TopoDS_Face& face); diff --git a/src/base/cpp_utils.h b/src/base/cpp_utils.h index e0ba1d0b..6e47a491 100644 --- a/src/base/cpp_utils.h +++ b/src/base/cpp_utils.h @@ -9,7 +9,6 @@ #include #include #include -#include #include #ifndef __cpp_lib_integer_comparison_functions # include @@ -18,23 +17,97 @@ namespace Mayo { +// Declare CppUtils namespace and its "short" alias +namespace CppUtils {} +namespace Cpp = CppUtils; + +// -- +// -- Declaration +// -- + +namespace CppUtils { + +// Type alias for the value type associated(mapped) to a key for an associative container(eg std::map) +template +using MappedType = typename AssociativeContainer::mapped_type; + +// Type alias for the key type of an associative container(eg std::map) +template +using KeyType = typename AssociativeContainer::key_type; + +// Returns a default empty std::string object, whose memory address can be used safely +inline const std::string& nullString(); + +// Finds in associative container the value mapped to 'key' +// If 'key' isn't found then a default-constructed value is returned +// The value(mapped) type must default-constructible +template +MappedType findValue( + const KeyType& key, + const AssociativeContainer& container +); + +// Toggles input boolean value(eg true->false) +inline void toggle(bool& value); + +// Same as std::cmp_equal() but provides an implementation if !__cpp_lib_integer_comparison_functions +template constexpr bool cmpEqual(T t, U u) noexcept; + +// Same as std::cmp_not_equal() but provides an implementation if !__cpp_lib_integer_comparison_functions +template constexpr bool cmpNotEqual(T t, U u) noexcept; + +// Same as std::cmp_less() but provides an implementation if !__cpp_lib_integer_comparison_functions +template constexpr bool cmpLess(T t, U u) noexcept; + +// Same as std::cmp_greater() but provides an implementation if !__cpp_lib_integer_comparison_functions +template constexpr bool cmpGreater(T t, U u) noexcept; + +// Same as std::cmp_less_equal() but provides an implementation if !__cpp_lib_integer_comparison_functions +template constexpr bool cmpLessEqual(T t, U u) noexcept; + +// Same as std::cmp_greater_equal() but provides an implementation if !__cpp_lib_integer_comparison_functions +template constexpr bool cmpGreaterEqual(T t, U u) noexcept; + +// Same as std::in_range() but provides an implementation if !__cpp_lib_integer_comparison_functions +template constexpr bool inRange(T t) noexcept; + +// Throws object of specified error type if 'condition' is met +template +void throwErrorIf(bool condition, ErrorArgs... args); + +// Same as static_cast(t) but throws exception if 't' does not fit inside type 'R' +template constexpr R safeStaticCast(T t); + +} // namespace CppUtils + +// -- +// -- Implementation +// -- + namespace CppUtils { -inline const std::string& nullString() +const std::string& nullString() { static std::string str; return str; } -template -ValueType findValue(const KeyType& key, const std::unordered_map& hashmap) +template +MappedType findValue( + const KeyType& key, const AssociativeContainer& container +) { - auto it = hashmap.find(key); - const ValueType defaultValue = {}; - return it != hashmap.cend() ? it->second : defaultValue; + auto it = container.find(key); + if (it != container.cend()) { + return it->second; + } + else { + const typename AssociativeContainer::mapped_type defaultValue = {}; + return defaultValue; + } } -inline void toggle(bool& value) +void toggle(bool& value) { value = !value; } @@ -124,7 +197,6 @@ template constexpr bool inRange(T t) noexcept #endif } -// Throws object of specified error type if 'condition' is met template void throwErrorIf(bool condition, ErrorArgs... args) { if (condition) { @@ -132,7 +204,6 @@ template void throwErrorIf(bool condi } } -// Same as static_cast(t) but throw exception if 't' does not fit inside type 'R' template constexpr R safeStaticCast(T t) { throwErrorIf(!inRange(t), "Value too big to fit inside range type"); @@ -140,4 +211,5 @@ template constexpr R safeStaticCast(T t) } } // namespace CppUtils + } // namespace Mayo diff --git a/src/base/document.h b/src/base/document.h index 8869af6c..fe3b9efe 100644 --- a/src/base/document.h +++ b/src/base/document.h @@ -19,6 +19,9 @@ namespace Mayo { +// Provides a data container, composed of labels and attributes +// It extends TDocStd_Document to provide an actualized model tree of its contents +// Entities are actually "root" data items class Document : public TDocStd_Document { public: using Identifier = int; // TODO alias TypedScalar diff --git a/src/base/document_tree_node.h b/src/base/document_tree_node.h index 3f6de7fe..7255a259 100644 --- a/src/base/document_tree_node.h +++ b/src/base/document_tree_node.h @@ -11,6 +11,7 @@ namespace Mayo { +// Provides a convenient item for model tree nodes within a Document object class DocumentTreeNode { public: DocumentTreeNode() = default; diff --git a/src/base/filepath_conv.h b/src/base/filepath_conv.h index 28767fde..cb898148 100644 --- a/src/base/filepath_conv.h +++ b/src/base/filepath_conv.h @@ -51,5 +51,11 @@ inline FilePath filepathFrom(std::string_view strUtf8) { return std_filesystem::u8path(strUtf8); } +// TCollection_AsciiString -> FilePath +// Assumes utf8 encoding +inline FilePath filepathFrom(const TCollection_AsciiString& strUtf8) { + return std_filesystem::u8path(strUtf8.ToCString()); +} + } // namespace Mayo diff --git a/src/base/io_format.cpp b/src/base/io_format.cpp index 4a93cc29..b7505b3d 100644 --- a/src/base/io_format.cpp +++ b/src/base/io_format.cpp @@ -27,6 +27,12 @@ std::string_view formatIdentifier(Format format) case Format_DXF: return "DXF"; case Format_PLY: return "PLY"; case Format_OFF: return "OFF"; + case Format_3DS: return "3DS"; + case Format_3MF: return "3MF"; + case Format_COLLADA: return "COLLADA"; + case Format_FBX: return "FBX"; + case Format_X3D: return "X3D"; + case Format_Blender: return "Blender"; } return ""; @@ -48,6 +54,12 @@ std::string_view formatName(Format format) case Format_DXF: return "Drawing Exchange Format"; case Format_PLY: return "Polygon File Format"; case Format_OFF: return "Object File Format"; + case Format_3DS: return "3DS Max File"; + case Format_3MF: return "33D Manufacturing Format"; + case Format_COLLADA: return "COLLAborative Design Activity(ISO/PAS 17506)"; + case Format_FBX: return "Filmbox"; + case Format_X3D: return "Extensible 3D Graphics(ISO/IEC 19775/19776/19777)"; + case Format_Blender: return "Blender File Format"; } return ""; @@ -55,33 +67,45 @@ std::string_view formatName(Format format) Span formatFileSuffixes(Format format) { - static std::string_view img_suffix[] = { "bmp", "jpeg", "jpg", "png", "gif", "ppm", "tiff" }; - static std::string_view step_suffix[] = { "step", "stp" }; - static std::string_view iges_suffix[] = { "iges", "igs" }; - static std::string_view occ_suffix[] = { "brep", "rle", "occ" }; - static std::string_view stl_suffix[] = { "stl" }; - static std::string_view obj_suffix[] = { "obj" }; - static std::string_view gltf_suffix[] = { "gltf", "glb" }; - static std::string_view vrml_suffix[] = { "wrl", "wrz", "vrml" }; - static std::string_view amf_suffix[] = { "amf" }; - static std::string_view dxf_suffix[] = { "dxf" }; - static std::string_view ply_suffix[] = { "ply" }; - static std::string_view off_suffix[] = { "off" }; + static std::string_view suffix_img[] = { "bmp", "jpeg", "jpg", "png", "gif", "ppm", "tiff" }; + static std::string_view suffix_3ds[] = { "3ds" }; + static std::string_view suffix_3mf[] = { "3mf" }; + static std::string_view suffix_amf[] = { "amf" }; + static std::string_view suffix_collada[] = { "dae", "zae" }; + static std::string_view suffix_dxf[] = { "dxf" }; + static std::string_view suffix_fbx[] = { "fbx" }; + static std::string_view suffix_gltf[] = { "gltf", "glb" }; + static std::string_view suffix_iges[] = { "iges", "igs" }; + static std::string_view suffix_obj[] = { "obj" }; + static std::string_view suffix_occ[] = { "brep", "rle", "occ" }; + static std::string_view suffix_off[] = { "off" }; + static std::string_view suffix_ply[] = { "ply" }; + static std::string_view suffix_step[] = { "step", "stp" }; + static std::string_view suffix_stl[] = { "stl" }; + static std::string_view suffix_vrml[] = { "wrl", "wrz", "vrml" }; + static std::string_view suffix_x3d[] = { "x3d", "x3dv", "x3db", "x3dz", "x3dbz", "x3dvz" }; + static std::string_view suffix_blender[] = { "blend", "blender", "blend1", "blend2" }; switch (format) { case Format_Unknown: return {}; - case Format_Image: return img_suffix; - case Format_STEP: return step_suffix; - case Format_IGES: return iges_suffix; - case Format_OCCBREP: return occ_suffix; - case Format_STL: return stl_suffix; - case Format_OBJ: return obj_suffix; - case Format_GLTF: return gltf_suffix; - case Format_VRML: return vrml_suffix; - case Format_AMF: return amf_suffix; - case Format_DXF: return dxf_suffix; - case Format_PLY: return ply_suffix; - case Format_OFF: return off_suffix; + case Format_Image: return suffix_img; + case Format_3DS: return suffix_3ds; + case Format_3MF: return suffix_3mf; + case Format_AMF: return suffix_amf; + case Format_COLLADA: return suffix_collada; + case Format_DXF: return suffix_dxf; + case Format_FBX: return suffix_fbx; + case Format_GLTF: return suffix_gltf; + case Format_IGES: return suffix_iges; + case Format_OBJ: return suffix_obj; + case Format_OCCBREP: return suffix_occ; + case Format_OFF: return suffix_off; + case Format_PLY: return suffix_ply; + case Format_STEP: return suffix_step; + case Format_STL: return suffix_stl; + case Format_VRML: return suffix_vrml; + case Format_X3D: return suffix_x3d; + case Format_Blender: return suffix_blender; } return {}; @@ -101,7 +125,8 @@ bool formatProvidesMesh(Format format) { return !formatProvidesBRep(format) && format != Format_Unknown - && format != Format_Image; + && format != Format_Image + ; } } // namespace IO diff --git a/src/base/io_format.h b/src/base/io_format.h index da7de0a8..081e746e 100644 --- a/src/base/io_format.h +++ b/src/base/io_format.h @@ -16,17 +16,23 @@ namespace IO { enum Format { Format_Unknown, Format_Image, - Format_STEP, + Format_3DS, + Format_3MF, + Format_AMF, + Format_COLLADA, + Format_DXF, + Format_FBX, + Format_GLTF, Format_IGES, + Format_OBJ, Format_OCCBREP, + Format_OFF, + Format_PLY, + Format_STEP, Format_STL, - Format_OBJ, - Format_GLTF, Format_VRML, - Format_AMF, - Format_DXF, - Format_PLY, - Format_OFF + Format_X3D, + Format_Blender }; // Returns identifier(unique short name) corresponding to 'format' diff --git a/src/base/io_system.cpp b/src/base/io_system.cpp index da47e5e4..47bfd7ac 100644 --- a/src/base/io_system.cpp +++ b/src/base/io_system.cpp @@ -216,7 +216,7 @@ bool System::importInDocument(const Args_ImportInDocument& args) if (taskData.fileFormat == Format_Unknown) return fnReadFileError(taskData.filepath, textIdTr("Unknown format")); - int portionSize = 40; + double portionSize = 40; if (fnEntityPostProcessRequired(taskData.fileFormat)) portionSize *= (100 - args.entityPostProcessProgressSize) / 100.; @@ -228,7 +228,8 @@ bool System::importInDocument(const Args_ImportInDocument& args) taskData.reader->setMessenger(messenger); if (args.parametersProvider) { taskData.reader->applyProperties( - args.parametersProvider->findReaderParameters(taskData.fileFormat)); + args.parametersProvider->findReaderParameters(taskData.fileFormat) + ); } if (!taskData.reader->readFile(taskData.filepath, &progress)) @@ -237,7 +238,7 @@ bool System::importInDocument(const Args_ImportInDocument& args) return true; }; auto fnTransfer = [&](TaskData& taskData) { - int portionSize = 60; + double portionSize = 60; if (fnEntityPostProcessRequired(taskData.fileFormat)) portionSize *= (100 - args.entityPostProcessProgressSize) / 100.; @@ -257,7 +258,8 @@ bool System::importInDocument(const Args_ImportInDocument& args) TaskProgress progress( taskData.progress, args.entityPostProcessProgressSize, - args.entityPostProcessProgressStep); + args.entityPostProcessProgressStep + ); const double subPortionSize = 100. / double(taskData.seqTransferredEntity.Size()); for (const TDF_Label& labelEntity : taskData.seqTransferredEntity) { TaskProgress subProgress(&progress, subPortionSize); @@ -419,11 +421,12 @@ System::Operation_ExportApplicationItems System::exportApplicationItems() void System::visitUniqueItems( Span spanItem, - std::function fnCallback) + std::function fnCallback + ) { std::unordered_set setDoc; for (const ApplicationItem& item : spanItem) { - if (item.isDocument()) { + if (item.isDocument() && item.document()->entityCount() > 0) { auto [it, ok] = setDoc.insert(item.document()); if (ok) fnCallback(item); @@ -446,7 +449,8 @@ void System::visitUniqueItems( void System::traverseUniqueItems( Span spanItem, std::function fnCallback, - TreeTraversal mode) + TreeTraversal mode + ) { System::visitUniqueItems(spanItem, [=](const ApplicationItem& item) { const DocumentPtr doc = item.document(); @@ -568,7 +572,8 @@ Format probeFormat_STL(const System::FormatProbeInput& input) bytes[offset] | (bytes[offset+1] << 8) | (bytes[offset+2] << 16) - | (bytes[offset+3] << 24); + | (bytes[offset+3] << 24) + ; constexpr unsigned facetSize = (sizeof(float) * 12) + sizeof(uint16_t); if ((facetSize * facetsCount + binaryStlHeaderSize) == input.hintFullSize) return Format_STL; diff --git a/src/base/libtree.h b/src/base/libtree.h index c802bffd..5ef92fce 100644 --- a/src/base/libtree.h +++ b/src/base/libtree.h @@ -9,6 +9,7 @@ #include "cpp_utils.h" #include "span.h" #include +#include #include namespace Mayo { diff --git a/src/base/math_utils.cpp b/src/base/math_utils.cpp index 99802260..4eda17e9 100644 --- a/src/base/math_utils.cpp +++ b/src/base/math_utils.cpp @@ -13,7 +13,6 @@ #include namespace Mayo { - namespace MathUtils { bool isReversedStandardDir(const gp_Dir& n) diff --git a/src/base/mesh_utils.cpp b/src/base/mesh_utils.cpp index 129e370e..04a375ee 100644 --- a/src/base/mesh_utils.cpp +++ b/src/base/mesh_utils.cpp @@ -6,17 +6,33 @@ #include "mesh_utils.h" #include "math_utils.h" -#include + +#include #include +#include namespace Mayo { +namespace MeshUtils { + +namespace { + +// Helper function to create TColStd_Array1OfReal +[[maybe_unused]] TColStd_Array1OfReal createArray1OfReal(int count) +{ + if (count > 0) + return TColStd_Array1OfReal(1, count); + else + return TColStd_Array1OfReal(); +} -double MeshUtils::triangleSignedVolume(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3) +} // namespace + +double triangleSignedVolume(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3) { return p1.Dot(p2.Crossed(p3)) / 6.0f; } -double MeshUtils::triangleArea(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3) +double triangleArea(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3) { const double ax = p2.X() - p1.X(); const double ay = p2.Y() - p1.Y(); @@ -30,7 +46,7 @@ double MeshUtils::triangleArea(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& return 0.5 * std::sqrt(cx*cx + cy*cy + cz*cz); } -double MeshUtils::triangulationVolume(const Handle_Poly_Triangulation& triangulation) +double triangulationVolume(const Handle_Poly_Triangulation& triangulation) { if (!triangulation) return 0; @@ -49,7 +65,7 @@ double MeshUtils::triangulationVolume(const Handle_Poly_Triangulation& triangula return std::abs(volume); } -double MeshUtils::triangulationArea(const Handle_Poly_Triangulation& triangulation) +double triangulationArea(const Handle_Poly_Triangulation& triangulation) { if (!triangulation) return 0; @@ -68,7 +84,7 @@ double MeshUtils::triangulationArea(const Handle_Poly_Triangulation& triangulati return area; } -void MeshUtils::setNode(const Handle_Poly_Triangulation& triangulation, int index, const gp_Pnt& pnt) +void setNode(const Handle_Poly_Triangulation& triangulation, int index, const gp_Pnt& pnt) { #if OCC_VERSION_HEX >= 0x070600 triangulation->SetNode(index, pnt); @@ -77,7 +93,7 @@ void MeshUtils::setNode(const Handle_Poly_Triangulation& triangulation, int inde #endif } -void MeshUtils::setTriangle(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangle& triangle) +void setTriangle(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangle& triangle) { #if OCC_VERSION_HEX >= 0x070600 triangulation->SetTriangle(index, triangle); @@ -86,19 +102,28 @@ void MeshUtils::setTriangle(const Handle_Poly_Triangulation& triangulation, int #endif } -void MeshUtils::setNormal(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangulation_NormalType& n) +void setNormal(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangulation_NormalType& n) { #if OCC_VERSION_HEX >= 0x070600 triangulation->SetNormal(index, n); #else TShort_Array1OfShortReal& normals = triangulation->ChangeNormals(); - normals.ChangeValue(index * 3 - 2) = n.X(); - normals.ChangeValue(index * 3 - 1) = n.Y(); - normals.ChangeValue(index * 3) = n.Z(); + normals.ChangeValue(index * 3 - 2) = static_cast(n.X()); + normals.ChangeValue(index * 3 - 1) = static_cast(n.Y()); + normals.ChangeValue(index * 3) = static_cast(n.Z()); +#endif +} + +void setUvNode(const Handle_Poly_Triangulation& triangulation, int index, double u, double v) +{ +#if OCC_VERSION_HEX >= 0x070600 + triangulation->SetUVNode(index, gp_Pnt2d{u, v}); +#else + triangulation->ChangeUVNode(index) = gp_Pnt2d{u, v}; #endif } -void MeshUtils::allocateNormals(const Handle_Poly_Triangulation& triangulation) +void allocateNormals(const Handle_Poly_Triangulation& triangulation) { #if OCC_VERSION_HEX >= 0x070600 triangulation->AddNormals(); @@ -108,8 +133,18 @@ void MeshUtils::allocateNormals(const Handle_Poly_Triangulation& triangulation) #endif } +const Poly_Array1OfTriangle& triangles(const Handle_Poly_Triangulation& triangulation) +{ +#if OCC_VERSION_HEX < 0x070600 + return triangulation->Triangles(); +#else + // Note: Poly_Triangulation::Triangles() was deprecated starting from OpenCascade v7.6.0 + return triangulation->InternalTriangles(); +#endif +} + // Adapted from http://cs.smith.edu/~jorourke/Code/polyorient.C -MeshUtils::Orientation MeshUtils::orientation(const AdaptorPolyline2d& polyline) +MeshUtils::Orientation orientation(const AdaptorPolyline2d& polyline) { const int pntCount = polyline.pointCount(); if (pntCount < 2) @@ -163,7 +198,7 @@ MeshUtils::Orientation MeshUtils::orientation(const AdaptorPolyline2d& polyline) } } -gp_Vec MeshUtils::directionAt(const AdaptorPolyline3d& polyline, int i) +gp_Vec directionAt(const AdaptorPolyline3d& polyline, int i) { const int pntCount = polyline.pointCount(); if (pntCount > 1) { @@ -182,4 +217,63 @@ gp_Vec MeshUtils::directionAt(const AdaptorPolyline3d& polyline, int i) return gp_Vec(); } +Polygon3dBuilder::Polygon3dBuilder(int nodeCount, ParametersOption option) +#if OCC_VERSION_HEX >= 0x070500 + : m_polygon(new Poly_Polygon3D(nodeCount, option == ParametersOption::With)), + m_ptrNodes(&m_polygon->ChangeNodes()), + m_ptrParams(option == ParametersOption::With ? &m_polygon->ChangeParameters() : nullptr) +#else + : m_nodes(1, nodeCount), + m_params(std::move(createArray1OfReal(option == ParametersOption::With ? nodeCount : 0))), + m_ptrNodes(&m_nodes), + m_ptrParams(option == ParametersOption::With ? &m_params : nullptr) +#endif +{ + assert(m_ptrNodes); + assert( + (option == ParametersOption::None && !m_ptrParams) + || (option == ParametersOption::With && m_ptrParams) + ); +} + +void Polygon3dBuilder::setNode(int i, const gp_Pnt &pnt) +{ + if (m_isFinalized) + throw std::runtime_error("Can't call setNode() on finalized Polygon3dBuilder object"); + + m_ptrNodes->ChangeValue(i) = pnt; +} + +void Polygon3dBuilder::setParameter(int i, double u) +{ + if (m_isFinalized) + throw std::runtime_error("Can't call setParameter() on finalized Polygon3dBuilder object"); + + if (m_ptrParams) + m_ptrParams->ChangeValue(i) = u; +} + +void Polygon3dBuilder::finalize() +{ + if (m_isFinalized) + return; + +#if OCC_VERSION_HEX < 0x070500 + if (m_ptrParams) + m_polygon = new Poly_Polygon3D(m_nodes, m_params); + else + m_polygon = new Poly_Polygon3D(m_nodes); +#endif + m_isFinalized = true; +} + +OccHandle Polygon3dBuilder::get() const +{ + if (!m_isFinalized) + throw std::runtime_error("Can't call get() on non finalized Polygon3dBuilder object"); + + return m_polygon; +} + +} // namespace MeshUtils } // namespace Mayo diff --git a/src/base/mesh_utils.h b/src/base/mesh_utils.h index 4175c25c..791045fc 100644 --- a/src/base/mesh_utils.h +++ b/src/base/mesh_utils.h @@ -6,61 +6,86 @@ #pragma once +#include "occ_handle.h" + +#include #include #include class gp_XYZ; namespace Mayo { -struct MeshUtils { - static double triangleSignedVolume(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3); - static double triangleArea(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3); +// Provides helper functions for mesh and triangle objects +namespace MeshUtils { + +double triangleSignedVolume(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3); +double triangleArea(const gp_XYZ& p1, const gp_XYZ& p2, const gp_XYZ& p3); - static double triangulationVolume(const Handle_Poly_Triangulation& triangulation); - static double triangulationArea(const Handle_Poly_Triangulation& triangulation); +double triangulationVolume(const Handle_Poly_Triangulation& triangulation); +double triangulationArea(const Handle_Poly_Triangulation& triangulation); #if OCC_VERSION_HEX >= 0x070600 - using Poly_Triangulation_NormalType = gp_Vec3f; +using Poly_Triangulation_NormalType = gp_Vec3f; #else - using Poly_Triangulation_NormalType = gp_Vec; +using Poly_Triangulation_NormalType = gp_Vec; #endif - static void setNode(const Handle_Poly_Triangulation& triangulation, int index, const gp_Pnt& pnt); - static void setTriangle(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangle& triangle); - static void setNormal(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangulation_NormalType& n); - static void allocateNormals(const Handle_Poly_Triangulation& triangulation); +void setNode(const Handle_Poly_Triangulation& triangulation, int index, const gp_Pnt& pnt); +void setTriangle(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangle& triangle); +void setNormal(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangulation_NormalType& n); +void setUvNode(const Handle_Poly_Triangulation& triangulation, int index, double u, double v); +void allocateNormals(const Handle_Poly_Triangulation& triangulation); - static const Poly_Array1OfTriangle& triangles(const Handle_Poly_Triangulation& triangulation) { -#if OCC_VERSION_HEX < 0x070600 - return triangulation->Triangles(); -#else - // Note: Poly_Triangulation::Triangles() was deprecated starting from OpenCascade v7.6.0 - return triangulation->InternalTriangles(); +const Poly_Array1OfTriangle& triangles(const Handle_Poly_Triangulation& triangulation); + +enum class Orientation { + Unknown, + Clockwise, + CounterClockwise +}; + +class AdaptorPolyline2d { +public: + virtual gp_Pnt2d pointAt(int index) const = 0; + virtual int pointCount() const = 0; + virtual bool empty() const { return this->pointCount() <= 0; } +}; + +class AdaptorPolyline3d { +public: + virtual const gp_Pnt& pointAt(int i) const = 0; + virtual int pointCount() const = 0; + virtual int empty() const { return this->pointCount() <= 0; } +}; + +Orientation orientation(const AdaptorPolyline2d& polyline); +gp_Vec directionAt(const AdaptorPolyline3d& polyline, int i); + +// Provides helper to create Poly_Polygon3D objects +// Poly_Polygon3D class interface changed from OpenCascade 7.4 to 7.5 version so using this class +// directly might cause compilation errors +// Prefer Polygon3dBuilder so application code doesn't have to care about OpenCascade version +class Polygon3dBuilder { +public: + enum class ParametersOption { None, With }; + + Polygon3dBuilder(int nodeCount, ParametersOption option = ParametersOption::None); + + void setNode(int i, const gp_Pnt& pnt); + void setParameter(int i, double u); + void finalize(); + OccHandle get() const; + +private: + bool m_isFinalized = false; + OccHandle m_polygon; +#if OCC_VERSION_HEX < 0x070500 + TColgp_Array1OfPnt m_nodes; + TColStd_Array1OfReal m_params; #endif - } - - enum class Orientation { - Unknown, - Clockwise, - CounterClockwise - }; - - class AdaptorPolyline2d { - public: - virtual gp_Pnt2d pointAt(int index) const = 0; - virtual int pointCount() const = 0; - virtual bool empty() const { return this->pointCount() <= 0; } - }; - - class AdaptorPolyline3d { - public: - virtual const gp_Pnt& pointAt(int i) const = 0; - virtual int pointCount() const = 0; - virtual int empty() const { return this->pointCount() <= 0; } - }; - - static Orientation orientation(const AdaptorPolyline2d& polyline); - static gp_Vec directionAt(const AdaptorPolyline3d& polyline, int i); + TColgp_Array1OfPnt* m_ptrNodes = nullptr; + TColStd_Array1OfReal* m_ptrParams = nullptr; }; +} // namespace MeshUtils } // namespace Mayo diff --git a/src/base/messenger.cpp b/src/base/messenger.cpp index fa299e03..23c1d064 100644 --- a/src/base/messenger.cpp +++ b/src/base/messenger.cpp @@ -6,6 +6,8 @@ #include "messenger.h" +#include + namespace Mayo { namespace { @@ -17,6 +19,47 @@ class NullMessenger : public Messenger { } // namespace +MessageStream::MessageStream(MessageType type, Messenger& messenger) + : m_type(type), m_messenger(messenger) +{ +} + +//Message::Message(const Message& other) +// : m_type(other.m_type), m_messenger(other.m_messenger), m_buffer(other.m_buffer) +//{ +//} + +MessageStream::~MessageStream() +{ + const std::string str = m_istream.str(); + switch (m_type) { + case MessageType::Trace: + m_messenger.emitTrace(str); + break; + case MessageType::Info: + m_messenger.emitInfo(str); + break; + case MessageType::Warning: + m_messenger.emitWarning(str); + break; + case MessageType::Error: + m_messenger.emitError(str); + break; + } +} + +MessageStream& MessageStream::space() +{ + m_istream << ' '; + return *this; +} + +MessageStream& MessageStream::operator<<(bool v) +{ + m_istream << (v ? "true" : "false"); + return *this; +} + void Messenger::emitTrace(std::string_view text) { this->emitMessage(MessageType::Trace, text); @@ -37,6 +80,26 @@ void Messenger::emitError(std::string_view text) this->emitMessage(MessageType::Error, text); } +MessageStream Messenger::trace() +{ + return MessageStream(MessageType::Trace, *this); +} + +MessageStream Messenger::info() +{ + return MessageStream(MessageType::Info, *this); +} + +MessageStream Messenger::warning() +{ + return MessageStream(MessageType::Warning, *this); +} + +MessageStream Messenger::error() +{ + return MessageStream(MessageType::Error, *this); +} + Messenger& Messenger::null() { static NullMessenger null; @@ -48,7 +111,7 @@ MessengerByCallback::MessengerByCallback(std::function +#include #include namespace Mayo { +class Messenger; + +enum class MessageType { + Trace, + Info, + Warning, + Error +}; + +// Provides stream-like syntax support, eg: +// messenger->info() << "Something happened, value: " << valueInt; +class MessageStream { +public: + MessageStream(MessageType type, Messenger& messenger); + MessageStream(const MessageStream&) = delete; + MessageStream& operator=(const MessageStream&) = delete; + ~MessageStream(); + + MessageType messageType() const { return m_type; } + std::istream& istream() { return m_istream; } + + MessageStream& space(); + MessageStream& operator<<(bool); + + template + MessageStream& operator<<(T t) { + m_istream << t; + return *this; + } + +private: + MessageType m_type = MessageType::Trace; + Messenger& m_messenger; + std::stringstream m_istream; +}; + +// Provides a general-purpose interface to issue text messages without knowledge of how these +// messages will be further processed class Messenger { public: - enum class MessageType { - Trace, - Info, - Warning, - Error - }; + // Dispatch the message 'text' to all observers + virtual void emitMessage(MessageType msgType, std::string_view text) = 0; + // Convenience functions around emitMessage() void emitTrace(std::string_view text); void emitInfo(std::string_view text); void emitWarning(std::string_view text); void emitError(std::string_view text); - virtual void emitMessage(MessageType msgType, std::string_view text) = 0; + + MessageStream trace(); + MessageStream info(); + MessageStream warning(); + MessageStream error(); static Messenger& null(); }; diff --git a/src/base/messenger_client.h b/src/base/messenger_client.h index 843a5750..81bb28ab 100644 --- a/src/base/messenger_client.h +++ b/src/base/messenger_client.h @@ -10,7 +10,7 @@ namespace Mayo { class Messenger; -// Provides a link to a messenger object +// Provides a link to a Messenger object // The object returned by MessengerClient::messenger() is guaranteed to be non-nullptr class MessengerClient { public: diff --git a/src/base/task.cpp b/src/base/occ_handle.h similarity index 58% rename from src/base/task.cpp rename to src/base/occ_handle.h index 5ae68143..387a2243 100644 --- a/src/base/task.cpp +++ b/src/base/occ_handle.h @@ -1,11 +1,16 @@ /**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. +** Copyright (c) 2023, Fougue Ltd. ** All rights reserved. ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ -#include "task.h" +#pragma once + +#include namespace Mayo { +// Template alias for OpenCascade handle +template using OccHandle = opencascade::handle; + } // namespace Mayo diff --git a/src/base/occ_progress_indicator.cpp b/src/base/occ_progress_indicator.cpp index 2527c050..4b684d20 100644 --- a/src/base/occ_progress_indicator.cpp +++ b/src/base/occ_progress_indicator.cpp @@ -43,8 +43,7 @@ bool OccProgressIndicator::Show(const bool /*force*/) if (m_progress) { m_progress->setStep(to_stdStringView(this->GetScope(1).GetName())); const double pc = this->GetPosition(); // Always within [0,1] - const int val = pc * 100; - m_progress->setValue(val); + m_progress->setValue(pc * 100); } return true; diff --git a/src/base/occ_progress_indicator.h b/src/base/occ_progress_indicator.h index b135eb03..39d83167 100644 --- a/src/base/occ_progress_indicator.h +++ b/src/base/occ_progress_indicator.h @@ -13,6 +13,7 @@ namespace Mayo { class TaskProgress; +// Provides implementation of OpenCascade-based progress indicator around Mayo::TaskProgress class OccProgressIndicator : public Message_ProgressIndicator { public: OccProgressIndicator(TaskProgress* progress); diff --git a/src/base/property.h b/src/base/property.h index df1a7116..e5a134a8 100644 --- a/src/base/property.h +++ b/src/base/property.h @@ -75,6 +75,7 @@ struct PropertyChangedBlocker { #define Mayo_PropertyChangedBlocker(group) \ [[maybe_unused]] Mayo::PropertyChangedBlocker __Mayo_PropertyChangedBlocker(group); +// Provides an abstract storage of a value with associated meta-data(name, description, ...) class Property { public: Property(PropertyGroup* group, const TextId& name); diff --git a/src/base/property_value_conversion.cpp b/src/base/property_value_conversion.cpp index 694a44d0..e44ae8c4 100644 --- a/src/base/property_value_conversion.cpp +++ b/src/base/property_value_conversion.cpp @@ -7,6 +7,7 @@ #include "property_value_conversion.h" #include "cpp_utils.h" +#include "filepath_conv.h" #include "math_utils.h" #include "property_builtins.h" #include "property_enumeration.h" @@ -32,18 +33,18 @@ namespace { static std::string toString(double value, int prec = 6) { #if __cpp_lib_to_chars - char buff[64] = {}; - auto toCharsFormat = std::chars_format::general; - auto resToChars = std::to_chars(std::begin(buff), std::end(buff), value, toCharsFormat, prec); - if (resToChars.ec != std::errc()) - throw std::runtime_error("value_too_large"); + char buff[64] = {}; + auto toCharsFormat = std::chars_format::general; + auto resToChars = std::to_chars(std::begin(buff), std::end(buff), value, toCharsFormat, prec); + if (resToChars.ec != std::errc()) + throw std::runtime_error("value_too_large"); - return std::string(buff, resToChars.ptr - buff); + return std::string(buff, resToChars.ptr - buff); #else - std::stringstream sstr; - sstr.precision(prec); - sstr << value; - return sstr.str(); + std::stringstream sstr; + sstr.precision(prec); + sstr << value; + return sstr.str(); #endif } @@ -205,8 +206,11 @@ bool PropertyValueConversion::fromVariant(Property* prop, const Variant& variant return fnError("Variant not convertible to string"); } else if (isType(prop)) { + // Note: explicit conversion from utf8 std::string to std::filesystem::path + // If character type of the source string is "char" then FilePath constructor assumes + // native narrow encoding(which might cause encoding issues) if (variant.isConvertibleToConstRefString()) - return ptr(prop)->setValue(variant.toConstRefString()); + return ptr(prop)->setValue(filepathFrom(variant.toConstRefString())); else return fnError("Variant expected to hold string"); } @@ -300,12 +304,17 @@ bool PropertyValueConversion::Variant::toBool(bool* ok) const int PropertyValueConversion::Variant::toInt(bool* ok) const { assignBoolPtr(ok, true); - if (std::holds_alternative(*this)) + if (std::holds_alternative(*this)) { return std::get(*this); - else if (std::holds_alternative(*this)) - return std::get(*this); - else if (std::holds_alternative(*this)) + } + else if (std::holds_alternative(*this)) { + auto dval = std::floor(std::get(*this)); + if (std::isgreaterequal(dval, INT_MIN) && std::islessequal(dval, INT_MAX)) + return static_cast(dval); + } + else if (std::holds_alternative(*this)) { return std::stoi(std::get(*this)); + } assignBoolPtr(ok, false); return 0; diff --git a/src/base/settings.cpp b/src/base/settings.cpp index bfc27840..219cb4a2 100644 --- a/src/base/settings.cpp +++ b/src/base/settings.cpp @@ -232,7 +232,7 @@ Settings::GroupIndex Settings::addGroup(std::string_view identifier) for (const Settings_Group& group : d->m_vecGroup) { if (group.identifier.key == identifier) - return GroupIndex(&group - &d->m_vecGroup.front()); + return GroupIndex(Span_itemIndex(d->m_vecGroup, group)); } d->m_vecGroup.push_back({}); @@ -327,9 +327,9 @@ Settings::SettingIndex Settings::findProperty(const Property* property) const for (const Settings_Section& section : group.vecSection) { for (const Settings_Setting& setting : section.vecSetting) { if (setting.property == property) { - const auto idSetting = &setting - §ion.vecSetting.front(); - const auto idSection = §ion - &group.vecSection.front(); - const auto idGroup = &group - &d->m_vecGroup.front(); + const auto idSetting = Span_itemIndex(section.vecSetting, setting); + const auto idSection = Span_itemIndex(group.vecSection, section); + const auto idGroup = Span_itemIndex(d->m_vecGroup, group); return SettingIndex(SectionIndex(GroupIndex(idGroup), idSection), idSetting); } } @@ -360,7 +360,7 @@ Settings::SettingIndex Settings::addSetting(Property* property, GroupIndex group // sectionDefault->identifier = "DEFAULT"; // sectionDefault->title = tr("DEFAULT"); sectionDefault->isDefault = true; - const SectionIndex sectionId(groupId, sectionDefault - &group.vecSection.front()); + const SectionIndex sectionId(groupId, Span_itemIndex(group.vecSection, *sectionDefault)); return this->addSetting(property, sectionId); } diff --git a/src/base/span.h b/src/base/span.h index 7fc492f3..f2e4e389 100644 --- a/src/base/span.h +++ b/src/base/span.h @@ -7,9 +7,29 @@ #pragma once #include +#include +#include namespace Mayo { template using Span = gsl::span; +// Returns the index of 'item' contained in 'span' +template +constexpr int Span_itemIndex(Span span, typename Span::const_reference item) +{ + assert(!span.empty()); + auto index = &item - &span.front(); + assert(index >= 0); + assert(index <= INT_MAX); + assert(&span[static_cast::size_type>(index)] == &item); + return static_cast(index); +} + +template +constexpr int Span_itemIndex(const Container& cont, typename Container::const_reference item) +{ + return Span_itemIndex(Span(cont), item); +} + } // namespace Mayo diff --git a/src/base/string_conv.h b/src/base/string_conv.h index 97dc9e22..4570ee52 100644 --- a/src/base/string_conv.h +++ b/src/base/string_conv.h @@ -6,6 +6,7 @@ #pragma once +#include #include #include #include @@ -49,6 +50,12 @@ TCollection_AsciiString to_OccAsciiString(const StringType& str) { return string_conv(str); } +// X -> Handle(TCollection_HAsciiString) +template +Handle(TCollection_HAsciiString) to_OccHandleHAsciiString(const StringType& str) { + return string_conv(str); +} + // X -> TCollection_ExtendedString template TCollection_ExtendedString to_OccExtString(const StringType& str) { @@ -122,6 +129,21 @@ template<> struct StringConv { } }; +// std::string_view -> Handle(TCollection_HAsciiString) +template<> struct StringConv { + static auto to(std::string_view str) { + Handle(TCollection_HAsciiString) hnd = new TCollection_HAsciiString(to_OccAsciiString(str)); + return hnd; + } +}; + +// std::string_view -> NCollection_Utf8String +template<> struct StringConv { + static auto to(std::string_view str) { + return NCollection_Utf8String(str.data(), static_cast(str.size())); + } +}; + // -- // -- Handle(TCollection_HAsciiString) -> X // -- @@ -166,6 +188,13 @@ template<> struct StringConv { } }; +// std::string -> NCollection_Utf8String +template<> struct StringConv { + static auto to(const std::string& str) { + return NCollection_Utf8String(str.c_str(), static_cast(str.size())); + } +}; + // -- // -- TCollection_AsciiString -> X // -- diff --git a/src/base/task.h b/src/base/task.h deleted file mode 100644 index 141ece57..00000000 --- a/src/base/task.h +++ /dev/null @@ -1,33 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#pragma once - -#include "task_common.h" - -#include - -namespace Mayo { - -class TaskManager; -class TaskProgress; - -using TaskJob = std::function; - -class Task { -public: - TaskId id() const { return m_id; } - const TaskJob& job() const { return m_fn; } - TaskManager* manager() const { return m_manager; } - -private: - friend class TaskManager; - TaskId m_id = 0; - TaskJob m_fn; - TaskManager* m_manager = nullptr; -}; - -} // namespace Mayo diff --git a/src/base/task_common.h b/src/base/task_common.h index 10c1514c..73dc2249 100644 --- a/src/base/task_common.h +++ b/src/base/task_common.h @@ -10,7 +10,13 @@ namespace Mayo { +// Task identifier type using TaskId = uint64_t; + +// Reserved value for null tasks +constexpr TaskId TaskId_null = UINT64_MAX; + +// Syntactic sugar for task auto-deletion flag(see TaskManager::run/exec()) enum class TaskAutoDestroy { On, Off }; } // namespace Mayo diff --git a/src/base/task_manager.cpp b/src/base/task_manager.cpp index c09b3e9c..dee1b779 100644 --- a/src/base/task_manager.cpp +++ b/src/base/task_manager.cpp @@ -6,18 +6,58 @@ #include "task_manager.h" -#include "application.h" #include "cpp_utils.h" #include "math_utils.h" +#include #include +#include +#include +#include +#include namespace Mayo { +// Helper struct providing all required data to manage a Task object +struct TaskManager::Entity { + TaskId taskId; + TaskJob taskJob; + TaskProgress taskProgress; + std::string title; + std::future control; + std::atomic isFinished = false; + TaskAutoDestroy autoDestroy = TaskAutoDestroy::On; +}; + +// Pimpl struct providing private(hidden) interface of TaskManager class +struct TaskManager::Private { + // Ctor + Private(TaskManager* mgr) : taskMgr(mgr) {} + + // Const/mutable functions to find an Entity from a task identifier. Returns null if not found + TaskManager::Entity* findEntity(TaskId id); + const TaskManager::Entity* findEntity(TaskId id) const; + + // Execute(synchronous) task entity, sending started/ended signals accordingly + void execEntity(TaskManager::Entity* entity); + + // Destroy finished task entities whose policy was set to TaskAutoDestroy::On + void cleanGarbage(); + + TaskManager* taskMgr = nullptr; + std::atomic taskIdSeq = {}; + std::unordered_map> mapEntity; +}; + +TaskManager::TaskManager() + : d(new Private(this)) +{ +} + TaskManager::~TaskManager() { // Make sure all tasks are really finished - for (const auto& mapPair : m_mapEntity) { + for (const auto& mapPair : d->mapEntity) { const std::unique_ptr& ptrEntity = mapPair.second; if (ptrEntity->control.valid()) ptrEntity->control.wait(); @@ -25,49 +65,51 @@ TaskManager::~TaskManager() // Erase the task from its container before destruction, this will allow TaskProgress destructor // to behave correctly(it calls TaskProgress::setValue()) - for (auto it = m_mapEntity.begin(); it != m_mapEntity.end(); ) - it = m_mapEntity.erase(it); + for (auto it = d->mapEntity.begin(); it != d->mapEntity.end(); ) + it = d->mapEntity.erase(it); + + delete d; } TaskId TaskManager::newTask(TaskJob fn) { - const TaskId taskId = m_taskIdSeq.fetch_add(1); + const TaskId taskId = d->taskIdSeq.fetch_add(1); std::unique_ptr ptrEntity(new Entity); - ptrEntity->task.m_id = taskId; - ptrEntity->task.m_fn = std::move(fn); - ptrEntity->task.m_manager = this; - ptrEntity->taskProgress.setTask(&ptrEntity->task); - m_mapEntity.insert({ taskId, std::move(ptrEntity) }); + ptrEntity->taskId = taskId; + ptrEntity->taskJob = std::move(fn); + ptrEntity->taskProgress.setTaskId(taskId); + ptrEntity->taskProgress.setTaskManager(this); + d->mapEntity.insert({ taskId, std::move(ptrEntity) }); return taskId; } void TaskManager::run(TaskId id, TaskAutoDestroy policy) { - this->cleanGarbage(); - Entity* entity = this->findEntity(id); + d->cleanGarbage(); + Entity* entity = d->findEntity(id); if (!entity) return; entity->isFinished = false; entity->autoDestroy = policy; - entity->control = std::async([=]{ this->execEntity(entity); }); + entity->control = std::async([=]{ d->execEntity(entity); }); } void TaskManager::exec(TaskId id, TaskAutoDestroy policy) { - this->cleanGarbage(); - Entity* entity = this->findEntity(id); + d->cleanGarbage(); + Entity* entity = d->findEntity(id); if (!entity) return; entity->isFinished = false; entity->autoDestroy = policy; - this->execEntity(entity); + d->execEntity(entity); } bool TaskManager::waitForDone(TaskId id, int msecs) { - Entity* entity = this->findEntity(id); + Entity* entity = d->findEntity(id); if (!entity) return true; @@ -84,82 +126,88 @@ bool TaskManager::waitForDone(TaskId id, int msecs) void TaskManager::requestAbort(TaskId id) { - Entity* entity = this->findEntity(id); + Entity* entity = d->findEntity(id); if (entity) { this->signalAbortRequested.send(id); entity->taskProgress.requestAbort(); } } +void TaskManager::foreachTask(const std::function& fn) +{ + for (const auto& mapPair : d->mapEntity) + fn(mapPair.first); +} + int TaskManager::progress(TaskId id) const { - const Entity* entity = this->findEntity(id); + const Entity* entity = d->findEntity(id); return entity ? entity->taskProgress.value() : 0; } int TaskManager::globalProgress() const { int taskAccumPct = 0; - for (const auto& mapPair : m_mapEntity) { + for (const auto& mapPair : d->mapEntity) { const std::unique_ptr& ptrEntity = mapPair.second; if (ptrEntity->taskProgress.value() > 0) taskAccumPct += ptrEntity->taskProgress.value(); } - return MathUtils::toPercent(taskAccumPct, 0, m_mapEntity.size() * 100); - //qDebug() << "taskCount=" << taskCount << " taskAccumPct=" << taskAccumPct << " newGlobalPct=" << newGlobalPct; + const auto pct = MathUtils::toPercent(taskAccumPct, 0, d->mapEntity.size() * 100); + return std::lround(pct); } const std::string& TaskManager::title(TaskId id) const { - const Entity* entity = this->findEntity(id); + const Entity* entity = d->findEntity(id); return entity ? entity->title : CppUtils::nullString(); } void TaskManager::setTitle(TaskId id, std::string_view title) { - Entity* entity = this->findEntity(id); + Entity* entity = d->findEntity(id); if (entity) entity->title = title; } -TaskManager::Entity* TaskManager::findEntity(TaskId id) +TaskManager::Entity* TaskManager::Private::findEntity(TaskId id) { - auto it = m_mapEntity.find(id); - return it != m_mapEntity.end() ? it->second.get() : nullptr; + auto it = this->mapEntity.find(id); + return it != this->mapEntity.end() ? it->second.get() : nullptr; } -const TaskManager::Entity* TaskManager::findEntity(TaskId id) const +const TaskManager::Entity* TaskManager::Private::findEntity(TaskId id) const { - auto it = m_mapEntity.find(id); - return it != m_mapEntity.cend() ? it->second.get() : nullptr; + auto it = this->mapEntity.find(id); + return it != this->mapEntity.cend() ? it->second.get() : nullptr; } -void TaskManager::execEntity(Entity* entity) +void TaskManager::Private::execEntity(Entity* entity) { if (!entity) return; - this->signalStarted.send(entity->task.id()); - const TaskJob& fn = entity->task.job(); + this->taskMgr->signalStarted.send(entity->taskId); + const TaskJob& fn = entity->taskJob; fn(&entity->taskProgress); if (!entity->taskProgress.isAbortRequested()) entity->taskProgress.setValue(100); - this->signalEnded.send(entity->task.id()); + this->taskMgr->signalEnded.send(entity->taskId); entity->isFinished = true; } -void TaskManager::cleanGarbage() +void TaskManager::Private::cleanGarbage() { - auto it = m_mapEntity.begin(); - while (it != m_mapEntity.end()) { + auto it = this->mapEntity.begin(); + while (it != this->mapEntity.end()) { Entity* entity = it->second.get(); if (entity->isFinished && entity->autoDestroy == TaskAutoDestroy::On) { if (entity->control.valid()) entity->control.wait(); - it = m_mapEntity.erase(it); + it = this->mapEntity.erase(it); } else { ++it; diff --git a/src/base/task_manager.h b/src/base/task_manager.h index 27d30c9b..9509794e 100644 --- a/src/base/task_manager.h +++ b/src/base/task_manager.h @@ -7,65 +7,86 @@ #pragma once #include "signal.h" -#include "task.h" #include "task_progress.h" -#include -#include -#include +#include #include #include -#include namespace Mayo { +// Piece of code to be executed as a task(ie with TaskManager::run/exec()) +using TaskJob = std::function; + +// Central class providing creation/execution/deletion of Task objects class TaskManager { public: + // Ctor & dtor + TaskManager(); ~TaskManager(); + // Not copyable + TaskManager(const TaskManager&) = delete; + TaskManager& operator=(const TaskManager&) = delete; + + // Allocates a new task entity in manager + // Returns the task identifier(unique in the scope of the owning TaskManager) TaskId newTask(TaskJob fn); + + // Asynchronous execution of job associated with task identifier 'id' + // By default destroy policy is set to 'On' meaning the task will be deleted at some point + // after its completion + // This function is based on std::async() so depending on the C++ stdlib implementation this is + // probably using a thread pool + // NOTE The task must have been allocated previously with newTask() void run(TaskId id, TaskAutoDestroy policy = TaskAutoDestroy::On); - void exec(TaskId id, TaskAutoDestroy policy = TaskAutoDestroy::On); // Synchronous + // Same as run() but execution of the task job is synchronous(it runs in the current thread + // just like a regular function call) + void exec(TaskId id, TaskAutoDestroy policy = TaskAutoDestroy::On); + + // Current progress of task identified by 'id' + // NOTE The task must have been allocated previously with newTask() int progress(TaskId id) const; + + // Current progress of all tasks int globalProgress() const; + // Title(description) of a task identified by 'id' const std::string& title(TaskId id) const; void setTitle(TaskId id, std::string_view title); + // Blocks the current thread until task of identifier 'id' has finished bool waitForDone(TaskId id, int msecs = -1); + + // Instructs the task of identifier 'id' to abort as soon as possible + // Task interruption relies on the task job for this: it has to check regularly the + // TaskProgress::isAbortRequested() flag and interrupt consequently void requestAbort(TaskId id); - template - void foreachTask(Function fn) { - for (const auto& mapPair : m_mapEntity) - fn(mapPair.first); - } + // Applies function 'fn' to each task + void foreachTask(const std::function& fn); - // Signals + // Signal emitted when some task execution has just started Signal signalStarted; + + // Signal emitted when the current step description of some task has changed Signal signalProgressStep; + + // Signal emitted when the current progress of some task has changed Signal signalProgressChanged; + + // Signal emitted when requestAbort() was called on some task Signal signalAbortRequested; + + // Signal emitted when some task execution has just ended(whatever stop condition: finished + // or aborted) Signal signalEnded; private: - struct Entity { - Task task; - TaskProgress taskProgress; - std::string title; - std::future control; - std::atomic isFinished = false; - TaskAutoDestroy autoDestroy = TaskAutoDestroy::On; - }; - - Entity* findEntity(TaskId id); - const Entity* findEntity(TaskId id) const; - void execEntity(Entity* entity); - void cleanGarbage(); - - std::atomic m_taskIdSeq = {}; - std::unordered_map> m_mapEntity; + struct Entity; + struct Private; + Private* const d = nullptr; }; } // namespace Mayo diff --git a/src/base/task_progress.cpp b/src/base/task_progress.cpp index f2d47c53..0a5207c2 100644 --- a/src/base/task_progress.cpp +++ b/src/base/task_progress.cpp @@ -5,7 +5,6 @@ ****************************************************************************/ #include "task_progress.h" -#include "task.h" #include "task_manager.h" #include @@ -14,13 +13,10 @@ namespace Mayo { -TaskProgress::TaskProgress() -{ -} - TaskProgress::TaskProgress(TaskProgress* parent, double portionSize, std::string_view step) : m_parent(parent), - m_task(parent ? parent->m_task : nullptr), + m_taskMgr(parent ? parent->m_taskMgr : nullptr), + m_taskId(parent ? parent->m_taskId : TaskId_null), m_portionSize(std::clamp(portionSize, 0., 100.)) { if (!step.empty()) @@ -41,22 +37,12 @@ TaskProgress& TaskProgress::null() bool TaskProgress::isNull() const { - return m_task == nullptr; -} - -TaskId TaskProgress::taskId() const -{ - return m_task ? m_task->id() : std::numeric_limits::max(); -} - -TaskManager* TaskProgress::taskManager() const -{ - return m_task ? m_task->manager() : nullptr; + return m_taskId == TaskId_null; } void TaskProgress::setValue(int pct) { - if (this->isNull()) + if (m_taskId == TaskId_null) return; if (m_isAbortRequested) @@ -68,25 +54,25 @@ void TaskProgress::setValue(int pct) return; if (m_parent) { - const int valueDeltaInParent = std::ceil((m_value - valueOnEntry) * (m_portionSize / 100.)); + const auto valueDeltaInParent = std::round((m_value - valueOnEntry) * (m_portionSize / 100.)); m_parent->setValue(m_parent->value() + valueDeltaInParent); } else { - m_task->manager()->signalProgressChanged.send(m_task->id(), m_value); + m_taskMgr->signalProgressChanged.send(m_taskId, m_value); } } -void TaskProgress::setStep(std::string_view title) +void TaskProgress::setValue(double pct) { - if (!this->isNull()) { - m_step = title; - m_task->manager()->signalProgressStep.send(m_task->id(), m_step); - } + this->setValue(static_cast(std::lround(pct))); } -void TaskProgress::setTask(const Task* task) +void TaskProgress::setStep(std::string_view title) { - m_task = task; + if (m_taskMgr && m_taskId != TaskId_null) { + m_step = title; + m_taskMgr->signalProgressStep.send(m_taskId, m_step); + } } bool TaskProgress::isAbortRequested(const TaskProgress* progress) @@ -96,7 +82,7 @@ bool TaskProgress::isAbortRequested(const TaskProgress* progress) void TaskProgress::requestAbort() { - if (!this->isNull()) + if (m_taskId != TaskId_null) m_isAbortRequested = true; } diff --git a/src/base/task_progress.h b/src/base/task_progress.h index 7d7d458d..b084303a 100644 --- a/src/base/task_progress.h +++ b/src/base/task_progress.h @@ -13,24 +13,25 @@ namespace Mayo { -class Task; class TaskManager; +// Provides feedback on the progress of a running/executing task class TaskProgress { public: - TaskProgress(); + TaskProgress() = default; TaskProgress(TaskProgress* parent, double portionSize, std::string_view step = {}); ~TaskProgress(); static TaskProgress& null(); bool isNull() const; - TaskId taskId() const; - TaskManager* taskManager() const; + TaskId taskId() const { return m_taskId; } + TaskManager* taskManager() const { return m_taskMgr; } // Value in [0,100] int value() const { return m_value; } void setValue(int pct); + void setValue(double pct); const std::string& step() const { return m_step; } void setStep(std::string_view title); @@ -49,13 +50,15 @@ class TaskProgress { TaskProgress& operator=(TaskProgress&&) = delete; private: - void setTask(const Task* task); + void setTaskId(TaskId id) { m_taskId = id; } + void setTaskManager(TaskManager* mgr) { m_taskMgr = mgr; } void requestAbort(); friend class TaskManager; TaskProgress* m_parent = nullptr; - const Task* m_task = nullptr; + TaskManager* m_taskMgr = nullptr; + TaskId m_taskId = TaskId_null; double m_portionSize = -1; std::atomic m_value = 0; std::string m_step; diff --git a/src/base/text_id.h b/src/base/text_id.h index e236c548..7e19effd 100644 --- a/src/base/text_id.h +++ b/src/base/text_id.h @@ -23,11 +23,19 @@ public: \ namespace Mayo { +// Provides translatable text(message) identifier struct TextId { + // Context used to find the text(key) to translate, typically a class name std::string_view trContext; + + // Source text std::string_view key; + // Returns the translation text of 'key' by querying registered Application::Translator objects + // 'n' is used to support plural forms std::string_view tr(int n = -1) const; + + // Whether source text(key) is empty or not bool isEmpty() const; }; diff --git a/src/base/tkernel_utils.h b/src/base/tkernel_utils.h index 84720b4d..3dd45009 100644 --- a/src/base/tkernel_utils.h +++ b/src/base/tkernel_utils.h @@ -25,6 +25,7 @@ class Message_ProgressIndicator; namespace Mayo { +// Provides helper functions for OpenCascade TKernel library class TKernelUtils { public: template diff --git a/src/base/unit_system.cpp b/src/base/unit_system.cpp index aba843da..6bff41f2 100644 --- a/src/base/unit_system.cpp +++ b/src/base/unit_system.cpp @@ -370,6 +370,11 @@ UnitSystem::TranslateResult UnitSystem::millimeters(QuantityLength length) return { length.value(), "mm", 1. }; } +UnitSystem::TranslateResult UnitSystem::squareMillimeters(QuantityArea area) +{ + return { area.value(), "mm²", 1. }; +} + UnitSystem::TranslateResult UnitSystem::cubicMillimeters(QuantityVolume volume) { return { volume.value(), "mm³", 1. }; diff --git a/src/base/unit_system.h b/src/base/unit_system.h index 5ffeffbd..e773e31a 100644 --- a/src/base/unit_system.h +++ b/src/base/unit_system.h @@ -38,6 +38,7 @@ class UnitSystem { static TranslateResult degrees(QuantityAngle angle); static TranslateResult meters(QuantityLength length); static TranslateResult millimeters(QuantityLength length); + static TranslateResult squareMillimeters(QuantityArea area); static TranslateResult cubicMillimeters(QuantityVolume volume); static TranslateResult millimetersPerSecond(QuantityVelocity speed); static TranslateResult milliseconds(QuantityTime duration); diff --git a/src/graphics/graphics_scene.cpp b/src/graphics/graphics_scene.cpp index 8160b838..c3cc49db 100644 --- a/src/graphics/graphics_scene.cpp +++ b/src/graphics/graphics_scene.cpp @@ -81,8 +81,7 @@ DEFINE_STANDARD_HANDLE(InteractiveContext, AIS_InteractiveContext) class GraphicsScene::Private { public: - Handle_V3d_Viewer m_v3dViewer; - Handle_InteractiveContext m_aisContext; + opencascade::handle m_aisContext; std::unordered_set m_setClipPlaneSensitive; bool m_isRedrawBlocked = false; SelectionMode m_selectionMode = SelectionMode::Single; @@ -91,23 +90,27 @@ class GraphicsScene::Private { GraphicsScene::GraphicsScene() : d(new Private) { - d->m_v3dViewer = Internal::createOccViewer(); - d->m_aisContext = new InteractiveContext(d->m_v3dViewer); + d->m_aisContext = new InteractiveContext(Internal::createOccViewer()); } GraphicsScene::~GraphicsScene() { + // Preventive cleaning fixes weird crash happening in MSVC Debug mode + d->m_aisContext->RemoveFilters(); + d->m_aisContext->Deactivate(); + d->m_aisContext->EraseAll(false); + d->m_aisContext->RemoveAll(false); delete d; } opencascade::handle GraphicsScene::createV3dView() { - return d->m_v3dViewer->CreateView(); + return this->v3dViewer()->CreateView(); } const opencascade::handle& GraphicsScene::v3dViewer() const { - return d->m_v3dViewer; + return d->m_aisContext->CurrentViewer(); } const opencascade::handle& GraphicsScene::mainSelector() const @@ -148,7 +151,7 @@ void GraphicsScene::redraw() return; //d->m_aisContext->UpdateCurrentViewer(); - for (auto itView = d->m_v3dViewer->DefinedViewIterator(); itView.More(); itView.Next()) + for (auto itView = this->v3dViewer()->DefinedViewIterator(); itView.More(); itView.Next()) this->signalRedrawRequested.send(itView.Value()); } @@ -157,7 +160,7 @@ void GraphicsScene::redraw(const Handle_V3d_View& view) if (d->m_isRedrawBlocked) return; - for (auto itView = d->m_v3dViewer->DefinedViewIterator(); itView.More(); itView.Next()) { + for (auto itView = this->v3dViewer()->DefinedViewIterator(); itView.More(); itView.Next()) { if (itView.Value() == view) { this->signalRedrawRequested.send(view); break; diff --git a/src/graphics/graphics_texture2d.h b/src/graphics/graphics_texture2d.h new file mode 100644 index 00000000..d46bcc29 --- /dev/null +++ b/src/graphics/graphics_texture2d.h @@ -0,0 +1,24 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include +#if OCC_VERSION_HEX >= 0x070700 +# include +#else +# include +#endif + +namespace Mayo { + +#if OCC_VERSION_HEX >= 0x070700 +using GraphicsTexture2D = Graphic3d_Texture2D; +#else +using GraphicsTexture2D = Graphic3d_Texture2Dmanual; +#endif + +} // namespace Mayo diff --git a/src/graphics/graphics_utils.cpp b/src/graphics/graphics_utils.cpp index 58eb565d..e33b1adb 100644 --- a/src/graphics/graphics_utils.cpp +++ b/src/graphics/graphics_utils.cpp @@ -28,9 +28,9 @@ static void AisContext_setObjectVisible( { if (ptrContext && object) { if (on) - ptrContext->Display(object, false); + ptrContext->Display(object, false/*dontUpdateViewer*/); else - ptrContext->Erase(object, false); + ptrContext->Erase(object, false/*dontUpdateViewer*/); } } @@ -39,7 +39,7 @@ static void AisContext_setObjectVisible( void GraphicsUtils::V3dView_fitAll(const Handle_V3d_View& view) { view->ZFitAll(); - view->FitAll(0.01, false); + view->FitAll(0.01, false/*dontUpdateView*/); } bool GraphicsUtils::V3dView_hasClipPlane( @@ -72,22 +72,57 @@ gp_Pnt GraphicsUtils::V3dView_to3dPosition(const Handle_V3d_View& view, double x const gp_Pln planeView(pntAt, dirEye); double px, py, pz; - const int ix = static_cast(std::round(x)); - const int iy = static_cast(std::round(y)); + const int ix = std::lround(x); + const int iy = std::lround(y); view->Convert(ix, iy, px, py, pz); const gp_Pnt pntConverted(px, py, pz); const gp_Pnt2d pntConvertedOnPlane = ProjLib::Project(planeView, pntConverted); return ElSLib::Value(pntConvertedOnPlane.X(), pntConvertedOnPlane.Y(), planeView); } +bool GraphicsUtils::V3dViewer_isGridActive(const Handle_V3d_Viewer& viewer) +{ +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + return viewer->IsGridActive(); +#else + return viewer->IsActive(); +#endif +} + +Handle_Aspect_Grid GraphicsUtils::V3dViewer_grid(const Handle_V3d_Viewer& viewer) +{ +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + return viewer->Grid(false/*dontCreate*/); +#else + return viewer->Grid(); +#endif +} + +GraphicsUtils::AspectGridColors GraphicsUtils::V3dViewer_gridColors(const Handle_V3d_Viewer& viewer) +{ + AspectGridColors colors; + Handle_Aspect_Grid gridAspect = V3dViewer_grid(viewer); + if (gridAspect) + gridAspect->Colors(colors.base, colors.tenth); + + return colors; +} + +void GraphicsUtils::V3dViewer_setGridColors(const Handle_V3d_Viewer &viewer, const AspectGridColors &colors) +{ + Handle_Aspect_Grid gridAspect = V3dViewer_grid(viewer); + if (gridAspect) + gridAspect->SetColors(colors.base, colors.tenth); +} + void GraphicsUtils::AisContext_eraseObject( const Handle_AIS_InteractiveContext& context, const Handle_AIS_InteractiveObject& object) { if (!object.IsNull() && !context.IsNull()) { - context->Erase(object, false); - context->Remove(object, false); - context->ClearPrs(object, 0, false); + context->Erase(object, false/*dontUpdateViewer*/); + context->Remove(object, false/*dontUpdateViewer*/); + context->ClearPrs(object, 0, false/*dontUpdateViewer*/); context->SelectionManager()->Remove(object); } } diff --git a/src/graphics/graphics_utils.h b/src/graphics/graphics_utils.h index b89ee67a..494a3ea8 100644 --- a/src/graphics/graphics_utils.h +++ b/src/graphics/graphics_utils.h @@ -9,24 +9,39 @@ #include "graphics_object_ptr.h" #include #include +#include #include #include +#include class Image_PixMap; namespace Mayo { struct GraphicsUtils { + + struct AspectGridColors { + Quantity_Color base; + Quantity_Color tenth; + }; + static void V3dView_fitAll(const Handle_V3d_View& view); static bool V3dView_hasClipPlane(const Handle_V3d_View& view, const Handle_Graphic3d_ClipPlane& plane); static gp_Pnt V3dView_to3dPosition(const Handle_V3d_View& view, double x, double y); + static bool V3dViewer_isGridActive(const Handle_V3d_Viewer& viewer); + static Handle_Aspect_Grid V3dViewer_grid(const Handle_V3d_Viewer& viewer); + static AspectGridColors V3dViewer_gridColors(const Handle_V3d_Viewer& viewer); + static void V3dViewer_setGridColors(const Handle_V3d_Viewer& viewer, const AspectGridColors& colors); + static void AisContext_eraseObject( const Handle_AIS_InteractiveContext& context, - const Handle_AIS_InteractiveObject& object); + const Handle_AIS_InteractiveObject& object + ); static void AisContext_setObjectVisible( const Handle_AIS_InteractiveContext& context, const Handle_AIS_InteractiveObject& object, - bool on); + bool on + ); static AIS_InteractiveContext* AisObject_contextPtr(const GraphicsObjectPtr& object); static bool AisObject_isVisible(const GraphicsObjectPtr& object); @@ -37,11 +52,14 @@ struct GraphicsUtils { static int AspectWindow_height(const Handle_Aspect_Window& wnd); static void Gfx3dClipPlane_setCappingHatch( - const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch); + const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch + ); static void Gfx3dClipPlane_setNormal( - const Handle_Graphic3d_ClipPlane& plane, const gp_Dir& n); + const Handle_Graphic3d_ClipPlane& plane, const gp_Dir& n + ); static void Gfx3dClipPlane_setPosition( - const Handle_Graphic3d_ClipPlane& plane, double pos); + const Handle_Graphic3d_ClipPlane& plane, double pos + ); static bool ImagePixmap_flipY(Image_PixMap& pixmap); }; diff --git a/src/graphics/graphics_view_ptr.h b/src/graphics/graphics_view_ptr.h index 7f15fe2d..c0400dd2 100644 --- a/src/graphics/graphics_view_ptr.h +++ b/src/graphics/graphics_view_ptr.h @@ -34,6 +34,8 @@ class GraphicsViewPtr { m_scene->redraw(m_view); } + const Handle_V3d_View& operator->() const { return m_view; } + private: GraphicsScene* m_scene = nullptr; Handle_V3d_View m_view; diff --git a/src/gui/gui_application.cpp b/src/gui/gui_application.cpp index 014df79c..4cd49b09 100644 --- a/src/gui/gui_application.cpp +++ b/src/gui/gui_application.cpp @@ -152,7 +152,8 @@ void GuiApplication::onDocumentAboutToClose(const DocumentPtr& doc) auto itFound = std::find_if( d->m_vecGuiDocument.begin(), d->m_vecGuiDocument.end(), - [=](const GuiDocument* guiDoc) { return guiDoc->document() == doc; }); + [=](const GuiDocument* guiDoc) { return guiDoc->document() == doc; } + ); if (itFound != d->m_vecGuiDocument.end()) { GuiDocument* guiDoc = *itFound; d->m_vecGuiDocument.erase(itFound); diff --git a/src/gui/gui_application.h b/src/gui/gui_application.h index 6478f334..fbf90072 100644 --- a/src/gui/gui_application.h +++ b/src/gui/gui_application.h @@ -18,6 +18,19 @@ namespace Mayo { class GuiDocument; +// Provides management of GuiDocument objects +// +// GuiApplication is connected to a "base" Application object: +// - when a Base::Document is created, a corresponding GuiDocument object is automatically created +// - when a Base::Document is closed, the mapped GuiDocument is automatically destroyed +// +// Typically application code should not create/destroy GuiDocument(s), this is the +// responsability of a central GuiApplication object. In some corner-case scenarios though, this +// behavior can be switched off with GuiApplication::setAutomaticDocumentMapping(false) +// +// GuiApplication acts also as a container of GraphicsObjectDriver objects. +// Those drivers are used by GuiDocument to automatically create 3D graphics representing the +// entities owned by the Base::Document class GuiApplication { public: GuiApplication(const ApplicationPtr& app); diff --git a/src/gui/gui_document.cpp b/src/gui/gui_document.cpp index d655a116..5e07c091 100644 --- a/src/gui/gui_document.cpp +++ b/src/gui/gui_document.cpp @@ -130,12 +130,12 @@ void GuiDocument::setDevicePixelRatio(double ratio) if (viewCube) { viewCube->SetSize(55 * m_devicePixelRatio, true/*adaptOtherParams*/); viewCube->SetFontHeight(12 * m_devicePixelRatio); - const int xyOffset = int(std::round(85 * m_devicePixelRatio)); + const int xyOffset = std::lround(85 * m_devicePixelRatio); viewCube->SetTransformPersistence( new Graphic3d_TransformPers( - Graphic3d_TMF_TriedronPers, - m_viewTrihedronCorner, - Graphic3d_Vec2i(xyOffset, xyOffset) + Graphic3d_TMF_TriedronPers, + m_viewTrihedronCorner, + Graphic3d_Vec2i(xyOffset, xyOffset) ) ); viewCube->Redisplay(true/*allModes*/); @@ -476,14 +476,17 @@ int GuiDocument::aisViewCubeBoundingSize() const #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) auto hnd = opencascade::handle::DownCast(m_aisViewCube); - return 2 * (hnd->Size() - + hnd->BoxFacetExtension() - + hnd->BoxEdgeGap() - + hnd->BoxEdgeMinSize() - + hnd->BoxCornerMinSize() - + hnd->RoundRadius()) - + hnd->AxesPadding() - + hnd->FontHeight(); + auto size = + 2 * (hnd->Size() + + hnd->BoxFacetExtension() + + hnd->BoxEdgeGap() + + hnd->BoxEdgeMinSize() + + hnd->BoxCornerMinSize() + + hnd->RoundRadius() + ) + + hnd->AxesPadding() + + hnd->FontHeight(); + return std::lround(size); #else return 0; #endif @@ -586,11 +589,15 @@ void GuiDocument::mapEntity(TreeNodeId entityTreeNodeId) } if (!docModelTree.nodeIsRoot(id)) { - const TDF_Label parentNodeLabel = docModelTree.nodeData(docModelTree.nodeParent(id)); + const TreeNodeId parentNodeId = docModelTree.nodeParent(id); + const TDF_Label parentNodeLabel = docModelTree.nodeData(parentNodeId); if (XCaf::isShapeReference(parentNodeLabel) && m_document->xcaf().hasShapeColor(parentNodeLabel)) { // Parent node is a reference and it redefines color attribute, so the graphics // can't be shared with the product auto gfxObject = m_guiApp->createGraphicsObject(parentNodeLabel); + const TreeNodeId grandParentNodeId = docModelTree.nodeParent(parentNodeId); + const TopLoc_Location locGrandParentShape = XCaf::shapeAbsoluteLocation(docModelTree, grandParentNodeId); + gfxObject->SetLocalTransformation(locGrandParentShape); gfxEntity.vecObject.push_back(gfxObject); } else { diff --git a/src/gui/v3d_view_controller.cpp b/src/gui/v3d_view_controller.cpp index ea41f46e..16fdbbae 100644 --- a/src/gui/v3d_view_controller.cpp +++ b/src/gui/v3d_view_controller.cpp @@ -146,7 +146,7 @@ void V3dViewController::startInstantZoom(const Position& currPos) { this->startDynamicAction(DynamicAction::InstantZoom); this->backupCamera(); - const int dX = m_instantZoomFactor * 100; + const int dX = std::lround(m_instantZoomFactor * 100); m_view->StartZoomAtPoint(currPos.x, currPos.y); m_view->ZoomAtPoint(currPos.x, currPos.y, currPos.x + dX, currPos.y); this->redrawView(); diff --git a/src/io_assimp/io_assimp.cpp b/src/io_assimp/io_assimp.cpp new file mode 100644 index 00000000..98d74fc3 --- /dev/null +++ b/src/io_assimp/io_assimp.cpp @@ -0,0 +1,97 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "io_assimp.h" +#include "io_assimp_reader.h" + +#ifdef HAVE_ASSIMP +# include +#endif + +namespace Mayo { +namespace IO { + +Span AssimpFactoryReader::formats() const +{ + static const Format array[] = { + Format_AMF, Format_3DS, Format_3MF, Format_COLLADA, Format_FBX, Format_X3D, Format_Blender + }; + return array; +} + +std::unique_ptr AssimpFactoryReader::create(Format format) const +{ + auto itFound = std::find(this->formats().begin(), this->formats().end(), format); + if (itFound != this->formats().end()) + return std::make_unique(); + + return {}; +} + +std::unique_ptr AssimpFactoryReader::createProperties(Format format, PropertyGroup* parentGroup) const +{ + auto itFound = std::find(this->formats().begin(), this->formats().end(), format); + if (itFound != this->formats().end()) + return AssimpReader::createProperties(parentGroup); + + return {}; +} + +std::string_view AssimpLib::strVersion() +{ + static std::string str; + + if (str.empty()) { + str += std::to_string(aiGetVersionMajor()) + + "." + std::to_string(aiGetVersionMinor()) +#ifndef NO_ASSIMP_aiGetVersionPatch + + "." + std::to_string(aiGetVersionPatch()) +#else + + ".?" +#endif + ; + } + + return str; +} + +std::string_view AssimpLib::strVersionDetails() +{ + static std::string str; + + if (str.empty()) { + const std::string strBranchName = aiGetBranchName() ? aiGetBranchName() : ""; + + std::string strCompileFlags; + { + const unsigned compileFlags = aiGetCompileFlags(); + auto addFlagIfDefined = [&](unsigned flag, const char* strFlag) { + if (compileFlags & flag) { + if (!strCompileFlags.empty()) + strCompileFlags += "|"; + strCompileFlags += strFlag; + } + }; + addFlagIfDefined(ASSIMP_CFLAGS_SHARED, "shared"); + addFlagIfDefined(ASSIMP_CFLAGS_STLPORT, "stlport"); + addFlagIfDefined(ASSIMP_CFLAGS_DEBUG, "debug"); + addFlagIfDefined(ASSIMP_CFLAGS_NOBOOST, "no-boost"); + addFlagIfDefined(ASSIMP_CFLAGS_SINGLETHREADED, "single-threaded"); +#ifdef ASSIMP_CFLAGS_DOUBLE_SUPPORT + addFlagIfDefined(ASSIMP_CFLAGS_DOUBLE_SUPPORT, "double-support"); +#endif + } + + str += "rev:" + std::to_string(aiGetVersionRevision()) + + " branch:" + (!strBranchName.empty() ? strBranchName : "?") + + " flags:" + strCompileFlags + ; + } + + return str; +} +} // namespace IO +} // namespace Mayo diff --git a/src/io_assimp/io_assimp.h b/src/io_assimp/io_assimp.h new file mode 100644 index 00000000..d8a25b04 --- /dev/null +++ b/src/io_assimp/io_assimp.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "../base/io_reader.h" +#include "../base/property.h" +#include + +namespace Mayo { +namespace IO { + +// Provides factory for Assimp-based Reader objects +class AssimpFactoryReader : public FactoryReader { +public: + Span formats() const override; + std::unique_ptr create(Format format) const override; + std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; + + static std::unique_ptr create() { +#ifdef HAVE_ASSIMP + return std::make_unique(); +#else + return {}; +#endif + } +}; + +struct AssimpLib { + static std::string_view strName() { return "Assimp"; } +#ifdef HAVE_ASSIMP + static std::string_view strVersion(); + static std::string_view strVersionDetails(); +#else + static std::string_view strVersion() { return ""; } + static std::string_view strVersionDetails() { return ""; } +#endif +}; + +} // namespace IO +} // namespace Mayo diff --git a/src/io_assimp/io_assimp_reader.cpp b/src/io_assimp/io_assimp_reader.cpp new file mode 100644 index 00000000..01400547 --- /dev/null +++ b/src/io_assimp/io_assimp_reader.cpp @@ -0,0 +1,686 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "io_assimp_reader.h" +#include "../base/brep_utils.h" +#include "../base/caf_utils.h" +#include "../base/cpp_utils.h" +#include "../base/document.h" +#include "../base/filepath_conv.h" +#include "../base/math_utils.h" +#include "../base/mesh_utils.h" +#include "../base/messenger.h" +#include "../base/occ_handle.h" +#include "../base/property.h" +#include "../base/string_conv.h" +#include "../base/task_progress.h" +#include "../base/xcaf.h" + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +//#define MAYO_ASSIMP_READER_HANDLE_SCALING 1 + +namespace Mayo { +namespace IO { + +namespace { + +struct AssimpReaderI18N { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::IO::AssimpReaderI18N) +}; + +// Retrieve the scaling component in assimp matrix 'trsf' +aiVector3D aiMatrixScaling(const aiMatrix4x4& trsf) +{ + const ai_real scalingX = aiVector3D(trsf.a1, trsf.a2, trsf.a3).Length(); + const ai_real scalingY = aiVector3D(trsf.b1, trsf.b2, trsf.b3).Length(); + const ai_real scalingZ = aiVector3D(trsf.c1, trsf.c2, trsf.c3).Length(); + return aiVector3D(scalingX, scalingY, scalingZ); +} + +bool hasScaleFactor(const gp_Trsf& trsf) +{ + const double topLocationScalePrec = +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + TopLoc_Location::ScalePrec(); +#else + 1.e-14; +#endif + return (Abs(Abs(trsf.ScaleFactor()) - 1.) > topLocationScalePrec) || trsf.IsNegative(); +} + +bool hasScaleFactor(const aiVector3D& scaling) +{ + return (!MathUtils::fuzzyEqual(scaling.x, 1) + || !MathUtils::fuzzyEqual(scaling.y, 1) + || !MathUtils::fuzzyEqual(scaling.z, 1) + || scaling.x < 0. || scaling.y < 0. || scaling.z < 0. + ); +} + +[[maybe_unused]] bool hasScaleFactor(const aiMatrix4x4& trsf) +{ + const aiVector3D scaling = aiMatrixScaling(trsf); + return hasScaleFactor(scaling); +} + +// Check if Assimp tree from 'node' contains a transformation having scale +bool deep_aiNodeTransformationHasScaling(const aiNode* node) +{ + const aiVector3D scaling = aiMatrixScaling(node->mTransformation); + if (!MathUtils::fuzzyEqual(scaling.x, 1) + || !MathUtils::fuzzyEqual(scaling.y, 1) + || !MathUtils::fuzzyEqual(scaling.z, 1) + || scaling.x < 0. || scaling.y < 0. || scaling.z < 0. + ) + { + //std::cout << "[TRACE] hasScaling: " << scaling.x << " " << scaling.y << " " <mNumChildren; ++ichild) { + if (deep_aiNodeTransformationHasScaling(node->mChildren[ichild])) + return true; + } + + return false; +} + +// Visit each node in Assimp tree and call 'fnCallback' +void deep_aiNodeVisit(const aiNode* node, const std::function& fnCallback) +{ + fnCallback(node); + for (unsigned ichild = 0; ichild < node->mNumChildren; ++ichild) + deep_aiNodeVisit(node->mChildren[ichild], fnCallback); +} + +// Returns the OpenCascade transformation converted from assimp matrix +gp_Trsf toOccTrsf(const aiMatrix4x4& matrix) +{ + // TODO Check scaling != 0 + const aiVector3D scaling = aiMatrixScaling(matrix); + gp_Trsf trsf; + trsf.SetValues( + matrix.a1 / scaling.x, matrix.a2 / scaling.x, matrix.a3 / scaling.x, matrix.a4, + matrix.b1 / scaling.y, matrix.b2 / scaling.y, matrix.b3 / scaling.y, matrix.b4, + matrix.c1 / scaling.z, matrix.c2 / scaling.z, matrix.c3 / scaling.z, matrix.c4 + ); + return trsf; +} + +// Returns the Quantity_Color object equivalent to assimp 'color' +Quantity_Color toOccColor(const aiColor4D& color, Quantity_TypeOfColor colorType = Quantity_TOC_RGB) +{ + return Quantity_Color(color.r, color.g, color.b, colorType); +} + +// Create an OpenCascade Image_Texture object from assimp texture +Handle(Image_Texture) createOccTexture(const aiTexture* texture) +{ + const auto textureWidth = texture->mWidth; + const auto textureHeight = texture->mHeight; + const auto textureSize = textureHeight == 0 ? textureWidth : 4 * textureWidth * textureHeight; + Handle(NCollection_Buffer) buff = new NCollection_Buffer( + NCollection_BaseAllocator::CommonBaseAllocator(), + textureSize + ); + auto textureData = reinterpret_cast(texture->pcData); + std::copy(textureData, textureData + textureSize, buff->ChangeData()); + return new Image_Texture(buff, texture->mFilename.C_Str()); +} + +// Create an OpenCascade Poly_Triangulation object from assimp mesh +// The input 'mesh' is assumed to contain only triangles +Handle(Poly_Triangulation) createOccTriangulation(const aiMesh* mesh) +{ + assert(mesh != nullptr); + assert(mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE); + + const unsigned textureIndex = 0; + const bool hasUvNodes = mesh->HasTextureCoords(textureIndex) && mesh->mNumUVComponents[textureIndex] == 2; + Handle(Poly_Triangulation) triangulation = new Poly_Triangulation(mesh->mNumVertices, mesh->mNumFaces, hasUvNodes); + + for (unsigned i = 0; i < mesh->mNumVertices; ++i) { + const aiVector3D& vertex = mesh->mVertices[i]; + MeshUtils::setNode(triangulation, i + 1, { vertex.x, vertex.y, vertex.z }); + } + + for (unsigned i = 0; i < mesh->mNumFaces; ++i) { + const auto indices = mesh->mFaces[i].mIndices; + assert(mesh->mFaces[i].mNumIndices == 3); + MeshUtils::setTriangle(triangulation, i + 1, Poly_Triangle(indices[0] + 1, indices[1] + 1, indices[2] + 1)); + } + + if (mesh->HasNormals()) { + MeshUtils::allocateNormals(triangulation); + for (unsigned i = 0; i < mesh->mNumVertices; ++i) { + using OccNormal = MeshUtils::Poly_Triangulation_NormalType; + const aiVector3D& normal = mesh->mNormals[i]; + MeshUtils::setNormal(triangulation, i + 1, OccNormal{ normal.x, normal.y, normal.z }); + } + } + + if (hasUvNodes) { + for (unsigned i = 0; i < mesh->mNumVertices; i++) { + const aiVector3D& t = mesh->mTextureCoords[textureIndex][i]; + MeshUtils::setUvNode(triangulation, i + 1, t.x, t.y); + } + } + + return triangulation; +} + +// Provides assimp progress handler for TaskProgress +class AssimpProgressHandler : public Assimp::ProgressHandler { +public: + AssimpProgressHandler(TaskProgress* progress) + : m_progress(progress) + {} + + bool Update(float percent = -1.f) override + { + if (TaskProgress::isAbortRequested(m_progress)) + return false; + + if (percent > 0) + m_progress->setValue(percent * 100); + + return true; + } + +private: + TaskProgress* m_progress = nullptr; +}; + +} // namespace + +// -- +// -- AssimpReader +// -- + +class AssimpReader::Properties : public PropertyGroup { +public: + Properties(PropertyGroup* parentGroup) + : PropertyGroup(parentGroup) + { + } +}; + +bool AssimpReader::readFile(const FilePath& filepath, TaskProgress* progress) +{ + m_vecTriangulation.clear(); + m_vecMaterial.clear(); + m_mapMaterialLabel.clear(); + m_mapNodeData.clear(); + m_mapEmbeddedTexture.clear(); + m_mapFileTexture.clear(); + + const unsigned flags = + aiProcess_Triangulate + | aiProcess_JoinIdenticalVertices + //| aiProcess_SortByPType /* Crashes with assimp-5.3.1 on Windows */ + + //| aiProcess_OptimizeGraph + //| aiProcess_TransformUVCoords + //| aiProcess_FlipUVs + //| aiProcess_FindInstances + //| aiProcess_PreTransformVertices + //| aiProcess_FixInfacingNormals + //| aiProcess_GlobalScale -> AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY + //| aiProcess_GenNormals + //| aiProcess_GenSmoothNormals + //| aiProcess_GenUVCoords + //| aiProcess_CalcTangentSpace + //| aiProcess_ValidateDataStructure + ; + //const unsigned flags = aiProcess_Triangulate | aiProcess_FlipUVs | aiProcess_CalcTangentSpace; + //m_importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, false); + m_importer.SetPropertyBool(AI_CONFIG_PP_PTV_KEEP_HIERARCHY, true); + m_importer.SetProgressHandler(new AssimpProgressHandler(progress)); + m_scene = m_importer.ReadFile(filepath.u8string(), flags); + if (!m_scene || (m_scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !m_scene->mRootNode) { + this->messenger()->emitError(m_importer.GetErrorString()); + return false; + } + +#ifndef MAYO_ASSIMP_READER_HANDLE_SCALING + // Apply "aiProcess_PreTransformVertices" post-processing step + // This avoids issues with any non-identity scaling matrix + // WARNING Assimp bones and animations are destructed by this step + if (deep_aiNodeTransformationHasScaling(m_scene->mRootNode)) { + m_scene = m_importer.ApplyPostProcessing(aiProcess_PreTransformVertices); + this->messenger()->emitTrace("aiProcess_PreTransformVertices ON"); + } +#endif + + // Create OpenCascade elements from the assimp meshes + // mesh of triangles -> Poly_Triangulation + // mesh lines -> Poly_Polygon3D + m_vecTriangulation.resize(m_scene->mNumMeshes); + std::fill(m_vecTriangulation.begin(), m_vecTriangulation.end(), nullptr); + for (unsigned i = 0; i < m_scene->mNumMeshes; ++i) { + const aiMesh* mesh = m_scene->mMeshes[i]; + if (mesh->mPrimitiveTypes & aiPrimitiveType_TRIANGLE) { + auto triangulation = createOccTriangulation(mesh); + m_vecTriangulation.at(i) = triangulation; + } + else if (mesh->mPrimitiveTypes & aiPrimitiveType_LINE) { + // TODO Create and add a Poly_Polygon3D object + this->messenger()->emitWarning(AssimpReaderI18N::textIdTr("LINE primitives not supported yet")); + } + else { + this->messenger()->emitWarning(AssimpReaderI18N::textIdTr("Some primitive not supported")); + } + } + + for (unsigned i = 0; i < m_scene->mNumTextures; ++i) { + const aiTexture* texture = m_scene->mTextures[i]; + m_mapEmbeddedTexture.insert({ texture, createOccTexture(texture) }); + } + + m_vecMaterial.resize(m_scene->mNumMaterials); + std::fill(m_vecMaterial.begin(), m_vecMaterial.end(), nullptr); + for (unsigned i = 0; i < m_scene->mNumMaterials; ++i) { + const aiMaterial* material = m_scene->mMaterials[i]; + m_vecMaterial.at(i) = this->createOccVisMaterial(material, filepath); + } + + return true; +} + +TDF_LabelSequence AssimpReader::transfer(DocumentPtr doc, TaskProgress* progress) +{ + if (!m_scene) + return {}; + + m_mapNodeData.clear(); + // Compute data for each aiNode object in the scene + deep_aiNodeVisit(m_scene->mRootNode, [=](const aiNode* node) { + aiNodeData nodeData; + auto itParentData = m_mapNodeData.find(node->mParent); + if (itParentData != m_mapNodeData.cend()) { + const aiNodeData& parentData = itParentData->second; + nodeData.aiAbsoluteTrsf = parentData.aiAbsoluteTrsf * node->mTransformation; + nodeData.occAbsoluteTrsf = parentData.occAbsoluteTrsf * toOccTrsf(node->mTransformation); + } + else { + nodeData.aiAbsoluteTrsf = node->mTransformation; + nodeData.occAbsoluteTrsf = toOccTrsf(node->mTransformation); + } + + m_mapNodeData.insert({ node, nodeData }); + }); + + // Compute count of meshes in the scene + int meshCount = 0; + deep_aiNodeVisit(m_scene->mRootNode, [&](const aiNode* node) { + for (unsigned imesh = 0; imesh < node->mNumMeshes; ++imesh) + ++meshCount; + }); + + // Add materials in target document + auto materialTool = doc->xcaf().visMaterialTool(); + for (const Handle_XCAFDoc_VisMaterial& material : m_vecMaterial) { + const TDF_Label label = materialTool->AddMaterial(material, material->RawName()->String()); + m_mapMaterialLabel.insert({ material, label }); + } + + // Create entities in target document + int imesh = 0; + const TopoDS_Shape shapeEntity = BRepUtils::makeEmptyCompound(); + const TDF_Label labelEntity = doc->xcaf().shapeTool()->AddShape(shapeEntity, true/*makeAssembly*/); + TDataStd_Name::Set(labelEntity, to_OccExtString(m_scene->mRootNode->mName.C_Str())); + this->transferSceneNode(m_scene->mRootNode, doc, labelEntity, [&](const aiMesh*) { + progress->setValue(MathUtils::toPercent(++imesh, 0, meshCount)); + }); + //doc->xcaf().shapeTool()->ComputeShapes(labelEntity); + doc->xcaf().shapeTool()->UpdateAssemblies(); + return CafUtils::makeLabelSequence({ labelEntity }); +} + +std::unique_ptr AssimpReader::createProperties(PropertyGroup* /*parentGroup*/) +{ + return {}; +} + +void AssimpReader::applyProperties(const PropertyGroup* group) +{ + auto ptr = dynamic_cast(group); + if (ptr) { + } +} + +Handle(Image_Texture) AssimpReader::findOccTexture( + const std::string& strFilepath, const FilePath& modelFilepath + ) +{ + // Texture might be embedded + { + // Note: aiScene::GetEmbeddedTextureAndIndex() isn't available for version < 5.1 + const aiTexture* texture = m_scene->GetEmbeddedTexture(strFilepath.c_str()); + Handle(Image_Texture) occTexture = Cpp::findValue(texture, m_mapEmbeddedTexture); + if (occTexture) + return occTexture; + } + + // Texture might have already been loaded from file + { + Handle(Image_Texture) texture = CppUtils::findValue(strFilepath, m_mapFileTexture); + if (texture) + return texture; + } + + // Fallback: load texture from filepath + + // Define texture "candidate" filepaths that will be tried + const FilePath textureFilepath = filepathFrom(strFilepath); + const FilePath textureFilepathCandidates[] = { + textureFilepath, + modelFilepath.parent_path() / textureFilepath, + modelFilepath.parent_path() / textureFilepath.filename() + }; + + const FilePath* ptrTextureFilepath = nullptr; + for (const FilePath& fp : textureFilepathCandidates) { + if (filepathExists(fp)) { + ptrTextureFilepath = &fp; + break; + } + } + + // Could find an existing filepath for the texture + if (ptrTextureFilepath) { + Handle(Image_Texture) texture = new Image_Texture(filepathTo(*ptrTextureFilepath)); + // Cache texture + m_mapFileTexture.insert({ strFilepath, texture }); + return texture; + } + + // Report warning "texture not found" + MessageStream msgWarning = this->messenger()->warning(); + msgWarning << fmt::format(AssimpReaderI18N::textIdTr("Texture not found: {}\nTried:"), strFilepath); + for (const FilePath& fp : textureFilepathCandidates) + msgWarning << "\n " << filepathCanonical(fp).make_preferred().u8string(); + + return {}; +} + +Handle(XCAFDoc_VisMaterial) AssimpReader::createOccVisMaterial( + const aiMaterial* material, const FilePath& modelFilepath + ) +{ + Handle(XCAFDoc_VisMaterial) mat = new XCAFDoc_VisMaterial; + + //mat->SetAlphaMode(Graphic3d_AlphaMode_Opaque); + + ai_real shininessMax = 1.; + std::string suffix = modelFilepath.extension().u8string(); + std::transform(suffix.cbegin(), suffix.cend(), suffix.begin(), [](char c) { + return std::tolower(c, std::locale::classic()); + }); + if (suffix == ".fbx") + shininessMax = 128.f; + else if (suffix == ".obj") + shininessMax = 1000.f; + + // Set name + { + aiString matName; + material->Get(AI_MATKEY_NAME, matName); + std::string_view vMatName{ matName.C_Str(), matName.length }; + mat->SetRawName(string_conv(vMatName)); + } + + // Backface culling + { + int flag; + if (material->Get(AI_MATKEY_TWOSIDED, flag) == aiReturn_SUCCESS) { +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + mat->SetFaceCulling( + flag ? + Graphic3d_TypeOfBackfacingModel_DoubleSided + : Graphic3d_TypeOfBackfacingModel_BackCulled + ); +#else + mat->SetDoubleSided(flag != 0); +#endif + } + } + + // Helper function to update some boolean flag if 'res' holds success status + auto fnUpdateDefinedFlag = [](bool* ptrDefinedFlag, aiReturn res) { + if (ptrDefinedFlag && res == aiReturn_SUCCESS) + *ptrDefinedFlag = true; + return res == aiReturn_SUCCESS; + }; + + // Helper function to get the color value of some property + auto fnGetColor4D = [=](const char* key, unsigned type, unsigned index, aiColor4D* ptrColor, bool* ptrDefinedFlag = nullptr) { + const aiReturn res = material->Get(key, type, index, *ptrColor); + // Some models have wrong color components outside [0, 1] range, and Quantity_Color reject + // them(exception in constructor) + ptrColor->r = std::clamp(ptrColor->r, 0.f, 1.f); + ptrColor->g = std::clamp(ptrColor->g, 0.f, 1.f); + ptrColor->b = std::clamp(ptrColor->b, 0.f, 1.f); + ptrColor->a = std::clamp(ptrColor->a, 0.f, 1.f); + return fnUpdateDefinedFlag(ptrDefinedFlag, res); + }; + + // Helper function to get the value(real) of some property + auto fnGetReal = [=](const char* key, unsigned type, unsigned index, ai_real* ptrValue, bool* ptrDefinedFlag = nullptr) { + const aiReturn res = material->Get(key, type, index, *ptrValue); + return fnUpdateDefinedFlag(ptrDefinedFlag, res); + }; + + // Helper function to get the texture path value of some property + auto fnGetTexture = [=](aiTextureType type, aiString* ptrTexture, bool* ptrDefinedFlag = nullptr) { + const aiReturn res = material->GetTexture(type, 0, ptrTexture); + return fnUpdateDefinedFlag(ptrDefinedFlag, res); + }; + + // Helper function around AssimpReader::findOccTexture() + auto fnFindOccTexture = [=](const aiString& strTexture) { + return this->findOccTexture(strTexture.C_Str(), modelFilepath); + }; + + // Common + XCAFDoc_VisMaterialCommon matCommon; + matCommon.IsDefined = false; + + { + aiColor4D color; + if (fnGetColor4D(AI_MATKEY_COLOR_AMBIENT, &color, &matCommon.IsDefined)) + matCommon.AmbientColor = toOccColor(color); + + if (fnGetColor4D(AI_MATKEY_COLOR_DIFFUSE, &color, &matCommon.IsDefined)) + matCommon.DiffuseColor = toOccColor(color); + + if (fnGetColor4D(AI_MATKEY_COLOR_SPECULAR, &color, &matCommon.IsDefined)) + matCommon.SpecularColor = toOccColor(color); + +#if 0 + ai_real factor; + if (fnGetReal(AI_MATKEY_SPECULAR_FACTOR, &factor, &matCommon.IsDefined)) + this->messenger()->trace() << "AI_MATKEY_SPECULAR_FACTOR: " << factor; +#endif + + if (fnGetColor4D(AI_MATKEY_COLOR_EMISSIVE, &color, &matCommon.IsDefined)) + matCommon.EmissiveColor = toOccColor(color); + } + + { + ai_real value; + if (fnGetReal(AI_MATKEY_OPACITY, &value, &matCommon.IsDefined)) + matCommon.Transparency = std::clamp(1.f - value, 0.f, 1.f); + + if (fnGetReal(AI_MATKEY_SHININESS, &value, &matCommon.IsDefined)) { + matCommon.Shininess = std::clamp(value / shininessMax, 0.f, 1.f); + //this->messenger()->trace() << "Shininess: " << value << " max: " << shininessMax; + } + } + + { + aiString texDiffuse; + if (fnGetTexture(aiTextureType_DIFFUSE, &texDiffuse, &matCommon.IsDefined)) + matCommon.DiffuseTexture = fnFindOccTexture(texDiffuse); + } + + // PBR + XCAFDoc_VisMaterialPBR matPbr; + matPbr.IsDefined = false; + + { + aiString strTexture; + if (fnGetTexture(aiTextureType_BASE_COLOR, &strTexture, &matPbr.IsDefined)) + matPbr.BaseColorTexture = fnFindOccTexture(strTexture); + + if (fnGetTexture(aiTextureType_METALNESS, &strTexture)) + matPbr.MetallicRoughnessTexture = fnFindOccTexture(strTexture); + + if (fnGetTexture(aiTextureType_EMISSION_COLOR, &strTexture, &matPbr.IsDefined)) + matPbr.EmissiveTexture = fnFindOccTexture(strTexture); + + if (fnGetTexture(aiTextureType_AMBIENT_OCCLUSION, &strTexture, &matPbr.IsDefined)) + matPbr.OcclusionTexture = fnFindOccTexture(strTexture); + + if (fnGetTexture(aiTextureType_NORMALS, &strTexture, &matPbr.IsDefined)) + matPbr.NormalTexture = fnFindOccTexture(strTexture); + } + +#ifdef AI_MATKEY_BASE_COLOR + { + aiColor4D color; + if (fnGetColor4D(AI_MATKEY_BASE_COLOR, &color, &matPbr.IsDefined)) + matPbr.BaseColor = Quantity_ColorRGBA(toOccColor(color), color.a); + } +#endif + + { + // TODO Handle EmissiveFactor + } + + { + ai_real value; +#ifdef AI_MATKEY_METALLIC_FACTOR + if (fnGetReal(AI_MATKEY_METALLIC_FACTOR, &value, &matPbr.IsDefined)) + matPbr.Metallic = std::clamp(value, 0.f, 1.f); +#endif + +#ifdef AI_MATKEY_ROUGHNESS_FACTOR + if (fnGetReal(AI_MATKEY_ROUGHNESS_FACTOR, &value, &matPbr.IsDefined)) + matPbr.Roughness = std::clamp(value, 0.f, 1.f); +#endif + + if (fnGetReal(AI_MATKEY_REFRACTI, &value, &matPbr.IsDefined)) { + // Refraction index must be in range [1.0, 3.0] + // If < 1 then an exception is thrown by Graphic3d_MaterialAspect::SetRefractionIndex() + matPbr.RefractionIndex = std::clamp(value, 1.f, 3.f); + } + } + + if (matCommon.IsDefined) + mat->SetCommonMaterial(matCommon); + + if (matPbr.IsDefined) + mat->SetPbrMaterial(matPbr); + + return mat; +} + +void AssimpReader::transferSceneNode( + const aiNode* node, + DocumentPtr targetDoc, + const TDF_Label& labelEntity, + const std::function& fnCallbackMesh + ) +{ + if (!node) + return; + + const std::string nodeName = node->mName.C_Str(); + + const aiNodeData nodeData = Cpp::findValue(node, m_mapNodeData); + gp_Trsf nodeAbsoluteTrsf = nodeData.occAbsoluteTrsf; + if (hasScaleFactor(nodeAbsoluteTrsf)) + nodeAbsoluteTrsf.SetScaleFactor(1.); + +#ifdef MAYO_ASSIMP_READER_HANDLE_SCALING + const aiVector3D nodeScaling = aiMatrixScaling(nodeData.aiAbsoluteTrsf); + const bool nodeHasScaling = hasScaleFactor(nodeScaling); +#endif + + // Produce shape corresponding to the node + for (unsigned imesh = 0; imesh < node->mNumMeshes; ++imesh) { + auto sceneMeshIndex = node->mMeshes[imesh]; + const aiMesh* mesh = m_scene->mMeshes[sceneMeshIndex]; + fnCallbackMesh(mesh); + auto triangulation = m_vecTriangulation.at(sceneMeshIndex); + if (!triangulation) + continue; // Skip + +#ifdef MAYO_ASSIMP_READER_HANDLE_SCALING + if (nodeHasScaling) { + triangulation = triangulation->Copy(); + for (int i = 1; i < triangulation->NbNodes(); ++i) { + gp_Pnt pnt = triangulation->Node(i); + pnt.SetX(pnt.X() * nodeScaling.x); + pnt.SetY(pnt.Y() * nodeScaling.y); + pnt.SetZ(pnt.Z() * nodeScaling.z); + MeshUtils::setNode(triangulation, i , pnt); + } + } +#endif + + TopoDS_Face face = BRepUtils::makeFace(triangulation); + face.Location(nodeAbsoluteTrsf); + const TDF_Label labelComponent = targetDoc->xcaf().shapeTool()->AddComponent(labelEntity, face); + const TDF_Label labelFace = targetDoc->xcaf().shapeReferred(labelComponent); + + if (mesh->mMaterialIndex < m_vecMaterial.size()) { + const OccHandle& material = m_vecMaterial.at(mesh->mMaterialIndex); + const TDF_Label materialLabel = Cpp::findValue(material, m_mapMaterialLabel); + if (!materialLabel.IsNull()) + targetDoc->xcaf().visMaterialTool()->SetShapeMaterial(labelFace, materialLabel); + else + this->messenger()->trace() << "Material not found(umap), index: " << mesh->mMaterialIndex; + } + + std::string shapeName = nodeName; + if (node->mNumMeshes > 1) { + shapeName += "_"; + if (mesh->mName.length > 0) + shapeName += mesh->mName.C_Str(); + else + shapeName += "mesh" + std::to_string(imesh); + } + + TDataStd_Name::Set(labelFace, to_OccExtString(shapeName)); + } + + // Process child nodes + for (unsigned ichild = 0; ichild < node->mNumChildren; ++ichild) + this->transferSceneNode(node->mChildren[ichild], targetDoc, labelEntity, fnCallbackMesh); +} + +} // namespace IO +} // namespace Mayo diff --git a/src/io_assimp/io_assimp_reader.h b/src/io_assimp/io_assimp_reader.h new file mode 100644 index 00000000..6593b7d9 --- /dev/null +++ b/src/io_assimp/io_assimp_reader.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/document_ptr.h" +#include "../base/io_reader.h" +#include "../base/tkernel_utils.h" + +#include + +#include +#include +#include + +#include +#include +#include + +struct aiMaterial; +struct aiMesh; +struct aiNode; +struct aiTexture; + +namespace Mayo { +namespace IO { + +// Assimp-based reader +// Requires OpenCascade >= v7.5.0(for XCAFDoc_VisMaterial) +class AssimpReader : public Reader { +public: + bool readFile(const FilePath& filepath, TaskProgress* progress) override; + TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) override; + + static std::unique_ptr createProperties(PropertyGroup* parentGroup); + void applyProperties(const PropertyGroup* params) override; + +private: + // Create OpenCascade texture object + // Parameter 'strFilepath' is the filepath to the texture as specified by the assimp material + // Parameter 'modelFilepath' is the filepath to the 3D model being imported with Reader::readFile() + Handle(Image_Texture) findOccTexture(const std::string& strFilepath, const FilePath& modelFilepath); + + // Create XCAFDoc_VisMaterial from assimp material + // Parameter 'modelFilepath' is the filepath to the 3D model being imported with Reader::readFile() + Handle(XCAFDoc_VisMaterial) createOccVisMaterial(const aiMaterial* material, const FilePath& modelFilepath); + + void transferSceneNode( + const aiNode* node, + DocumentPtr targetDoc, + const TDF_Label& labelEntity, + const std::function& fnCallbackMesh + ); + + struct aiNodeData { + gp_Trsf occAbsoluteTrsf; + aiMatrix4x4 aiAbsoluteTrsf; + }; + + class Properties; + Assimp::Importer m_importer; + const aiScene* m_scene = nullptr; + + std::vector m_vecTriangulation; + std::vector m_vecMaterial; + std::unordered_map m_mapMaterialLabel; + std::unordered_map m_mapNodeData; + std::unordered_map m_mapEmbeddedTexture; + std::unordered_map m_mapFileTexture; +}; + +} // namespace IO +} // namespace Mayo diff --git a/src/io_dxf/dxf.cpp b/src/io_dxf/dxf.cpp index 730be108..ab432b5b 100644 --- a/src/io_dxf/dxf.cpp +++ b/src/io_dxf/dxf.cpp @@ -3,28 +3,138 @@ // This program is released under the BSD license. See the file COPYING for details. // modified 2018 wandererfan -// MAYO: file initially taken from FreeCad/src/Mod/Import/App/dxf.cpp -- commit #1ac35d2 +// MAYO: file taken from FreeCad/src/Mod/Import/App/dxf.cpp -- commit #55292e9 #if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES) //required by windows for M_PI definition # define _USE_MATH_DEFINES #endif +#include +#include +#include #include +#if __cpp_lib_to_chars +# include +#endif +#include #include +#include #include "../base/filepath.h" #include "dxf.h" +#include + namespace { -template -void safe_strcpy(char (&dst)[N1], const char (&src)[N2]) { - strncpy(dst, src, std::min(N1, N2)); -} +class ScopedCLocale { +public: + ScopedCLocale(int category) + : m_category(category), + m_savedLocale(std::setlocale(category, nullptr)) + { + std::setlocale(category, "C"); + } + + ~ScopedCLocale() + { + std::setlocale(m_category, m_savedLocale); + } + +private: + int m_category = 0; + const char* m_savedLocale = nullptr; +}; } // namespace +namespace DxfPrivate { + +template bool isStringToErrorValue(T value) +{ + if constexpr(std::is_same_v) { + return value == std::numeric_limits::max(); + } + else if constexpr(std::is_same_v) { + return value == std::numeric_limits::max(); + } + else if constexpr(std::is_same_v) { + constexpr double dMax = std::numeric_limits::max(); + return std::abs(value - dMax) * 1000000000000. <= std::min(std::abs(value), std::abs(dMax)); + } + else { + return false; + } +} + +template +T stringToNumeric(const std::string& line, StringToErrorMode errorMode) +{ +#if __cpp_lib_to_chars + T value; + auto [ptr, err] = std::from_chars(line.c_str(), line.c_str() + line.size(), value); + if (err == std::errc()) + return value; +#else + try { + if constexpr(std::is_same_v) { + return std::stoi(line); + } + else if constexpr(std::is_same_v) { + return std::stoul(line); + } + else if constexpr(std::is_same_v) { + return std::stod(line); + } + else { + if (errorMode == StringToErrorMode::ReturnErrorValue) + return std::numeric_limits::max(); + else + throw std::runtime_error("Failed to fetch numeric value from line:\n" + line); + } + } catch (...) { +#endif + + if (errorMode == StringToErrorMode::ReturnErrorValue) { + return std::numeric_limits::max(); + } + else { + std::string strTypeName = "?"; + if constexpr(std::is_same_v) + strTypeName = "int"; + else if constexpr(std::is_same_v) + strTypeName = "unsigned"; + else if constexpr(std::is_same_v) + strTypeName = "double"; + + throw std::runtime_error("Failed to fetch " + strTypeName + " value from line:\n" + line); + } + +#ifndef __cpp_lib_to_chars + } +#endif +} + +int stringToInt(const std::string& line, StringToErrorMode errorMode) +{ + return stringToNumeric(line, errorMode); +} + +unsigned stringToUnsigned(const std::string& line, StringToErrorMode errorMode) +{ + return stringToNumeric(line, errorMode); +} + +double stringToDouble(const std::string& line, StringToErrorMode errorMode) +{ + return stringToNumeric(line, errorMode); +} + +} // namespace DxfPrivate + +using namespace DxfPrivate; + Base::Vector3d toVector3d(const double* a) { Base::Vector3d result; @@ -34,27 +144,27 @@ Base::Vector3d toVector3d(const double* a) return result; } -CDxfWrite::CDxfWrite(const char* filepath) : -m_ofs(filepath, std::ios::out), -//TODO: these should probably be parameters in config file -//handles: -//boilerplate 0 - A00 -//used by dxf.cpp A01 - FFFE -//ACAD HANDSEED FFFF +CDxfWrite::CDxfWrite(const char* filepath) + : m_ofs(filepath, std::ios::out), + //TODO: these should probably be parameters in config file + //handles: + //boilerplate 0 - A00 + //used by dxf.cpp A01 - FFFE + //ACAD HANDSEED FFFF -m_handle(0xA00), //room for 2560 handles in boilerplate files -//m_entityHandle(0x300), //don't need special ranges for handles -//m_layerHandle(0x30), -//m_blockHandle(0x210), -//m_blkRecordHandle(0x110), -m_polyOverride(false), -m_layerName("none") + m_handle(0xA00), //room for 2560 handles in boilerplate files + //m_entityHandle(0x300), //don't need special ranges for handles + //m_layerHandle(0x30), + //m_blockHandle(0x210), + //m_blkRecordHandle(0x110), + m_polyOverride(false), + m_layerName("none") { // start the file m_fail = false; m_version = 12; - if(!m_ofs) + if (!m_ofs) m_fail = true; else m_ofs.imbue(std::locale::classic()); @@ -64,7 +174,7 @@ CDxfWrite::~CDxfWrite() { } -void CDxfWrite::init(void) +void CDxfWrite::init() { writeHeaderSection(); makeBlockRecordTableHead(); @@ -72,7 +182,7 @@ void CDxfWrite::init(void) } //! assemble pieces into output file -void CDxfWrite::endRun(void) +void CDxfWrite::endRun() { makeLayerTable(); makeBlockRecordTableBody(); @@ -90,18 +200,9 @@ void CDxfWrite::endRun(void) //*************************** //writeHeaderSection //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeHeaderSection(void) +void CDxfWrite::writeHeaderSection() { std::stringstream ss; -#if 0 - ss << "FreeCAD v" - << App::Application::Config()["BuildVersionMajor"] - << "." - << App::Application::Config()["BuildVersionMinor"] - << " " - << App::Application::Config()["BuildRevision"]; -#endif - //header & version m_ofs << "999" << std::endl; m_ofs << ss.str() << std::endl; @@ -117,7 +218,7 @@ void CDxfWrite::writeHeaderSection(void) //*************************** //writeClassesSection //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeClassesSection(void) +void CDxfWrite::writeClassesSection() { if (m_version < 14) { return; @@ -133,7 +234,7 @@ void CDxfWrite::writeClassesSection(void) //*************************** //writeTablesSection //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeTablesSection(void) +void CDxfWrite::writeTablesSection() { //static tables section head end content std::stringstream ss; @@ -162,7 +263,7 @@ void CDxfWrite::writeTablesSection(void) //*************************** //makeLayerTable //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::makeLayerTable(void) +void CDxfWrite::makeLayerTable() { std::string tablehash = getLayerHandle(); m_ssLayer << " 0" << std::endl; @@ -230,63 +331,63 @@ void CDxfWrite::makeLayerTable(void) //*************************** //makeBlockRecordTableHead //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::makeBlockRecordTableHead(void) +void CDxfWrite::makeBlockRecordTableHead() { if (m_version < 14) { return; } - std::string tablehash = getBlkRecordHandle(); - m_saveBlockRecordTableHandle = tablehash; - m_ssBlkRecord << " 0" << std::endl; - m_ssBlkRecord << "TABLE" << std::endl; - m_ssBlkRecord << " 2" << std::endl; - m_ssBlkRecord << "BLOCK_RECORD" << std::endl; - m_ssBlkRecord << " 5" << std::endl; - m_ssBlkRecord << tablehash << std::endl; - m_ssBlkRecord << "330" << std::endl; - m_ssBlkRecord << "0" << std::endl; - m_ssBlkRecord << "100" << std::endl; - m_ssBlkRecord << "AcDbSymbolTable" << std::endl; - m_ssBlkRecord << " 70" << std::endl; - m_ssBlkRecord << (m_blockList.size() + 5) << std::endl; - - m_saveModelSpaceHandle = getBlkRecordHandle(); - m_ssBlkRecord << " 0" << std::endl; - m_ssBlkRecord << "BLOCK_RECORD" << std::endl; - m_ssBlkRecord << " 5" << std::endl; - m_ssBlkRecord << m_saveModelSpaceHandle << std::endl; - m_ssBlkRecord << "330" << std::endl; - m_ssBlkRecord << tablehash << std::endl; - m_ssBlkRecord << "100" << std::endl; - m_ssBlkRecord << "AcDbSymbolTableRecord" << std::endl; - m_ssBlkRecord << "100" << std::endl; - m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; - m_ssBlkRecord << " 2" << std::endl; - m_ssBlkRecord << "*MODEL_SPACE" << std::endl; -// m_ssBlkRecord << " 1" << std::endl; -// m_ssBlkRecord << " " << std::endl; - - m_savePaperSpaceHandle = getBlkRecordHandle(); - m_ssBlkRecord << " 0" << std::endl; - m_ssBlkRecord << "BLOCK_RECORD" << std::endl; - m_ssBlkRecord << " 5" << std::endl; - m_ssBlkRecord << m_savePaperSpaceHandle << std::endl; - m_ssBlkRecord << "330" << std::endl; - m_ssBlkRecord << tablehash << std::endl; - m_ssBlkRecord << "100" << std::endl; - m_ssBlkRecord << "AcDbSymbolTableRecord" << std::endl; - m_ssBlkRecord << "100" << std::endl; - m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; - m_ssBlkRecord << " 2" << std::endl; - m_ssBlkRecord << "*PAPER_SPACE" << std::endl; -// m_ssBlkRecord << " 1" << std::endl; -// m_ssBlkRecord << " " << std::endl; + std::string tablehash = getBlkRecordHandle(); + m_saveBlockRecordTableHandle = tablehash; + m_ssBlkRecord << " 0" << std::endl; + m_ssBlkRecord << "TABLE" << std::endl; + m_ssBlkRecord << " 2" << std::endl; + m_ssBlkRecord << "BLOCK_RECORD" << std::endl; + m_ssBlkRecord << " 5" << std::endl; + m_ssBlkRecord << tablehash << std::endl; + m_ssBlkRecord << "330" << std::endl; + m_ssBlkRecord << "0" << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbSymbolTable" << std::endl; + m_ssBlkRecord << " 70" << std::endl; + m_ssBlkRecord << (m_blockList.size() + 5) << std::endl; + + m_saveModelSpaceHandle = getBlkRecordHandle(); + m_ssBlkRecord << " 0" << std::endl; + m_ssBlkRecord << "BLOCK_RECORD" << std::endl; + m_ssBlkRecord << " 5" << std::endl; + m_ssBlkRecord << m_saveModelSpaceHandle << std::endl; + m_ssBlkRecord << "330" << std::endl; + m_ssBlkRecord << tablehash << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbSymbolTableRecord" << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; + m_ssBlkRecord << " 2" << std::endl; + m_ssBlkRecord << "*MODEL_SPACE" << std::endl; + // m_ssBlkRecord << " 1" << std::endl; + // m_ssBlkRecord << " " << std::endl; + + m_savePaperSpaceHandle = getBlkRecordHandle(); + m_ssBlkRecord << " 0" << std::endl; + m_ssBlkRecord << "BLOCK_RECORD" << std::endl; + m_ssBlkRecord << " 5" << std::endl; + m_ssBlkRecord << m_savePaperSpaceHandle << std::endl; + m_ssBlkRecord << "330" << std::endl; + m_ssBlkRecord << tablehash << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbSymbolTableRecord" << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; + m_ssBlkRecord << " 2" << std::endl; + m_ssBlkRecord << "*PAPER_SPACE" << std::endl; + // m_ssBlkRecord << " 1" << std::endl; + // m_ssBlkRecord << " " << std::endl; } - + //*************************** //makeBlockRecordTableBody //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::makeBlockRecordTableBody(void) +void CDxfWrite::makeBlockRecordTableBody() { if (m_version < 14) { return; @@ -306,8 +407,8 @@ void CDxfWrite::makeBlockRecordTableBody(void) m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; m_ssBlkRecord << " 2" << std::endl; m_ssBlkRecord << b << std::endl; -// m_ssBlkRecord << " 70" << std::endl; -// m_ssBlkRecord << " 0" << std::endl; + // m_ssBlkRecord << " 70" << std::endl; + // m_ssBlkRecord << " 0" << std::endl; iBlkRecord++; } } @@ -315,7 +416,7 @@ void CDxfWrite::makeBlockRecordTableBody(void) //*************************** //makeBlockSectionHead //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::makeBlockSectionHead(void) +void CDxfWrite::makeBlockSectionHead() { m_ssBlock << " 0" << std::endl; m_ssBlock << "SECTION" << std::endl; @@ -432,8 +533,7 @@ std::string CDxfWrite::getPlateFile(const std::string& fileSpec) std::string line; std::ifstream inFile (fpath); - while (!inFile.eof()) - { + while (!inFile.eof()) { std::getline(inFile,line); if (!inFile.eof()) { outString << line << '\n'; @@ -443,7 +543,7 @@ std::string CDxfWrite::getPlateFile(const std::string& fileSpec) return outString.str(); } -std::string CDxfWrite::getHandle(void) +std::string CDxfWrite::getHandle() { m_handle++; std::stringstream ss; @@ -452,24 +552,44 @@ std::string CDxfWrite::getHandle(void) return ss.str(); } -std::string CDxfWrite::getEntityHandle(void) +std::string CDxfWrite::getEntityHandle() { return getHandle(); + // m_entityHandle++; + // std::stringstream ss; + // ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); + // ss << m_entityHandle; + // return ss.str(); } -std::string CDxfWrite::getLayerHandle(void) +std::string CDxfWrite::getLayerHandle() { return getHandle(); + // m_layerHandle++; + // std::stringstream ss; + // ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); + // ss << m_layerHandle; + // return ss.str(); } -std::string CDxfWrite::getBlockHandle(void) +std::string CDxfWrite::getBlockHandle() { return getHandle(); + // m_blockHandle++; + // std::stringstream ss; + // ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); + // ss << m_blockHandle; + // return ss.str(); } -std::string CDxfWrite::getBlkRecordHandle(void) +std::string CDxfWrite::getBlkRecordHandle() { return getHandle(); + // m_blkRecordHandle++; + // std::stringstream ss; + // ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); + // ss << m_blkRecordHandle; + // return ss.str(); } void CDxfWrite::addBlockName(std::string b, std::string h) @@ -480,8 +600,8 @@ void CDxfWrite::addBlockName(std::string b, std::string h) void CDxfWrite::setLayerName(std::string s) { - m_layerName = s; - m_layerList.push_back(s); + m_layerName = s; + m_layerList.push_back(s); } void CDxfWrite::writeLine(const double* s, const double* e) @@ -489,9 +609,11 @@ void CDxfWrite::writeLine(const double* s, const double* e) putLine(toVector3d(s),toVector3d(e),m_ssEntity, getEntityHandle(), m_saveModelSpaceHandle); } -void CDxfWrite::putLine(const Base::Vector3d& s, const Base::Vector3d& e, - std::ostringstream& outStream, const std::string& handle, - const std::string& ownerHandle) +void CDxfWrite::putLine(const Base::Vector3d& s, + const Base::Vector3d& e, + std::ostringstream& outStream, + const std::string& handle, + const std::string& ownerHandle) { outStream << " 0" << std::endl; outStream << "LINE" << std::endl; @@ -551,11 +673,11 @@ void CDxfWrite::writeLWPolyLine(const LWPolyDataOut &pd) m_ssEntity << pd.Flag << std::endl; m_ssEntity << " 43" << std::endl; m_ssEntity << "0" << std::endl; //Constant width opt -// m_ssEntity << pd.Width << std::endl; //Constant width opt -// m_ssEntity << " 38" << std::endl; -// m_ssEntity << pd.Elev << std::endl; // Elevation -// m_ssEntity << " 39" << std::endl; -// m_ssEntity << pd.Thick << std::endl; // Thickness + // m_ssEntity << pd.Width << std::endl; //Constant width opt + // m_ssEntity << " 38" << std::endl; + // m_ssEntity << pd.Elev << std::endl; // Elevation + // m_ssEntity << " 39" << std::endl; + // m_ssEntity << pd.Thick << std::endl; // Thickness for (auto& p: pd.Verts) { m_ssEntity << " 10" << std::endl; // Vertices m_ssEntity << p.x << std::endl; @@ -574,12 +696,12 @@ void CDxfWrite::writeLWPolyLine(const LWPolyDataOut &pd) m_ssEntity << " 42" << std::endl; m_ssEntity << b << std::endl; } -// m_ssEntity << "210" << std::endl; //Extrusion dir -// m_ssEntity << pd.Extr.x << std::endl; -// m_ssEntity << "220" << std::endl; -// m_ssEntity << pd.Extr.y << std::endl; -// m_ssEntity << "230" << std::endl; -// m_ssEntity << pd.Extr.z << std::endl; + // m_ssEntity << "210" << std::endl; //Extrusion dir + // m_ssEntity << pd.Extr.x << std::endl; + // m_ssEntity << "220" << std::endl; + // m_ssEntity << pd.Extr.y << std::endl; + // m_ssEntity << "230" << std::endl; + // m_ssEntity << pd.Extr.z << std::endl; } //*************************** @@ -671,7 +793,7 @@ void CDxfWrite::writeArc(const double* s, const double* e, const double* c, bool double start_angle = atan2(ay, ax) * 180/M_PI; double end_angle = atan2(by, bx) * 180/M_PI; double radius = sqrt(ax*ax + ay*ay); - if(!dir){ + if (!dir){ double temp = start_angle; start_angle = end_angle; end_angle = temp; @@ -688,9 +810,9 @@ void CDxfWrite::writeArc(const double* s, const double* e, const double* c, bool } m_ssEntity << " 8" << std::endl; // Group code for layer name m_ssEntity << getLayerName() << std::endl; // Layer number -// m_ssEntity << " 62" << std::endl; -// m_ssEntity << " 0" << std::endl; - if (m_version > 12) { + // m_ssEntity << " 62" << std::endl; + // m_ssEntity << " 0" << std::endl; + if (m_version > 12) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDbCircle" << std::endl; } @@ -727,7 +849,7 @@ void CDxfWrite::writeCircle(const double* c, double radius) } m_ssEntity << " 8" << std::endl; // Group code for layer name m_ssEntity << getLayerName() << std::endl; // Layer number - if (m_version > 12) { + if (m_version > 12) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDbCircle" << std::endl; } @@ -735,14 +857,18 @@ void CDxfWrite::writeCircle(const double* c, double radius) m_ssEntity << c[0] << std::endl; // X in WCS coordinates m_ssEntity << " 20" << std::endl; m_ssEntity << c[1] << std::endl; // Y in WCS coordinates -// m_ssEntity << " 30" << std::endl; -// m_ssEntity << c[2] << std::endl; // Z in WCS coordinates + // m_ssEntity << " 30" << std::endl; + // m_ssEntity << c[2] << std::endl; // Z in WCS coordinates m_ssEntity << " 40" << std::endl; // m_ssEntity << radius << std::endl; // Radius } -void CDxfWrite::writeEllipse(const double* c, double major_radius, double minor_radius, - double rotation, double start_angle, double end_angle, +void CDxfWrite::writeEllipse(const double* c, + double major_radius, + double minor_radius, + double rotation, + double start_angle, + double end_angle, bool endIsCW) { double m[3]; @@ -752,7 +878,7 @@ void CDxfWrite::writeEllipse(const double* c, double major_radius, double minor_ double ratio = minor_radius/major_radius; - if(!endIsCW){ //end is NOT CW from start + if (!endIsCW){ //end is NOT CW from start double temp = start_angle; start_angle = end_angle; end_angle = temp; @@ -769,7 +895,7 @@ void CDxfWrite::writeEllipse(const double* c, double major_radius, double minor_ } m_ssEntity << " 8" << std::endl; // Group code for layer name m_ssEntity << getLayerName() << std::endl; // Layer number - if (m_version > 12) { + if (m_version > 12) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDbEllipse" << std::endl; } @@ -787,12 +913,12 @@ void CDxfWrite::writeEllipse(const double* c, double major_radius, double minor_ m_ssEntity << m[2] << std::endl; // Major Z m_ssEntity << " 40" << std::endl; // m_ssEntity << ratio << std::endl; // Ratio -// m_ssEntity << "210" << std::endl; //extrusion dir?? -// m_ssEntity << "0" << std::endl; -// m_ssEntity << "220" << std::endl; -// m_ssEntity << "0" << std::endl; -// m_ssEntity << "230" << std::endl; -// m_ssEntity << "1" << std::endl; + // m_ssEntity << "210" << std::endl; //extrusion dir?? + // m_ssEntity << "0" << std::endl; + // m_ssEntity << "220" << std::endl; + // m_ssEntity << "0" << std::endl; + // m_ssEntity << "230" << std::endl; + // m_ssEntity << "1" << std::endl; m_ssEntity << " 41" << std::endl; m_ssEntity << start_angle << std::endl; // Start angle (radians [0..2pi]) m_ssEntity << " 42" << std::endl; @@ -838,18 +964,18 @@ void CDxfWrite::writeSpline(const SplineDataOut &sd) m_ssEntity << " 74" << std::endl; m_ssEntity << 0 << std::endl; -// m_ssEntity << " 12" << std::endl; -// m_ssEntity << sd.starttan.x << std::endl; -// m_ssEntity << " 22" << std::endl; -// m_ssEntity << sd.starttan.y << std::endl; -// m_ssEntity << " 32" << std::endl; -// m_ssEntity << sd.starttan.z << std::endl; -// m_ssEntity << " 13" << std::endl; -// m_ssEntity << sd.endtan.x << std::endl; -// m_ssEntity << " 23" << std::endl; -// m_ssEntity << sd.endtan.y << std::endl; -// m_ssEntity << " 33" << std::endl; -// m_ssEntity << sd.endtan.z << std::endl; + // m_ssEntity << " 12" << std::endl; + // m_ssEntity << sd.starttan.x << std::endl; + // m_ssEntity << " 22" << std::endl; + // m_ssEntity << sd.starttan.y << std::endl; + // m_ssEntity << " 32" << std::endl; + // m_ssEntity << sd.starttan.z << std::endl; + // m_ssEntity << " 13" << std::endl; + // m_ssEntity << sd.endtan.x << std::endl; + // m_ssEntity << " 23" << std::endl; + // m_ssEntity << sd.endtan.y << std::endl; + // m_ssEntity << " 33" << std::endl; + // m_ssEntity << sd.endtan.z << std::endl; for (auto& k: sd.knot) { m_ssEntity << " 40" << std::endl; @@ -910,21 +1036,33 @@ void CDxfWrite::writeVertex(double x, double y, double z) m_ssEntity << 0 << std::endl; } -void CDxfWrite::writeText(const char* text, const double* location1, const double* location2, - const double height, const int horizJust) -{ - putText(text, toVector3d(location1), toVector3d(location2), - height, horizJust, - m_ssEntity, getEntityHandle(), m_saveModelSpaceHandle); +void CDxfWrite::writeText(const char* text, + const double* location1, + const double* location2, + const double height, + const int horizJust) +{ + putText(text, + toVector3d(location1), + toVector3d(location2), + height, + horizJust, + m_ssEntity, + getEntityHandle(), + m_saveModelSpaceHandle); } //*************************** //putText //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::putText(const char* text, const Base::Vector3d& location1, const Base::Vector3d& location2, - const double height, const int horizJust, - std::ostringstream& outStream, const std::string& handle, - const std::string& ownerHandle) +void CDxfWrite::putText(const char* text, + const Base::Vector3d& location1, + const Base::Vector3d& location2, + const double height, + const int horizJust, + std::ostringstream& outStream, + const std::string& handle, + const std::string& ownerHandle) { (void) location2; @@ -944,8 +1082,8 @@ void CDxfWrite::putText(const char* text, const Base::Vector3d& location1, const outStream << "100" << std::endl; outStream << "AcDbText" << std::endl; } -// outStream << " 39" << std::endl; -// outStream << 0 << std::endl; //thickness + // outStream << " 39" << std::endl; + // outStream << 0 << std::endl; //thickness outStream << " 10" << std::endl; //first alignment point outStream << location1.x << std::endl; outStream << " 20" << std::endl; @@ -956,33 +1094,33 @@ void CDxfWrite::putText(const char* text, const Base::Vector3d& location1, const outStream << height << std::endl; outStream << " 1" << std::endl; outStream << text << std::endl; -// outStream << " 50" << std::endl; -// outStream << 0 << std::endl; //rotation -// outStream << " 41" << std::endl; -// outStream << 1 << std::endl; -// outStream << " 51" << std::endl; -// outStream << 0 << std::endl; + // outStream << " 50" << std::endl; + // outStream << 0 << std::endl; //rotation + // outStream << " 41" << std::endl; + // outStream << 1 << std::endl; + // outStream << " 51" << std::endl; + // outStream << 0 << std::endl; outStream << " 7" << std::endl; outStream << "STANDARD" << std::endl; //style -// outStream << " 71" << std::endl; //default -// outStream << "0" << std::endl; + // outStream << " 71" << std::endl; //default + // outStream << "0" << std::endl; outStream << " 72" << std::endl; outStream << horizJust << std::endl; -//// outStream << " 73" << std::endl; -//// outStream << "0" << std::endl; + //// outStream << " 73" << std::endl; + //// outStream << "0" << std::endl; outStream << " 11" << std::endl; //second alignment point outStream << location2.x << std::endl; outStream << " 21" << std::endl; outStream << location2.y << std::endl; outStream << " 31" << std::endl; outStream << location2.z << std::endl; -// outStream << "210" << std::endl; -// outStream << "0" << std::endl; -// outStream << "220" << std::endl; -// outStream << "0" << std::endl; -// outStream << "230" << std::endl; -// outStream << "1" << std::endl; + // outStream << "210" << std::endl; + // outStream << "0" << std::endl; + // outStream << "220" << std::endl; + // outStream << "0" << std::endl; + // outStream << "230" << std::endl; + // outStream << "1" << std::endl; if (m_version > 12) { outStream << "100" << std::endl; outStream << "AcDbText" << std::endl; @@ -990,8 +1128,11 @@ void CDxfWrite::putText(const char* text, const Base::Vector3d& location1, const } -void CDxfWrite::putArrow(const Base::Vector3d& arrowPos, const Base::Vector3d& barb1Pos, const Base::Vector3d& barb2Pos, - std::ostringstream& outStream, const std::string& handle, +void CDxfWrite::putArrow(const Base::Vector3d& arrowPos, + const Base::Vector3d& barb1Pos, + const Base::Vector3d& barb2Pos, + std::ostringstream& outStream, + const std::string& handle, const std::string& ownerHandle) { outStream << " 0" << std::endl; @@ -1044,9 +1185,12 @@ void CDxfWrite::putArrow(const Base::Vector3d& arrowPos, const Base::Vector3d& b #define ALIGNED 0 #define HORIZONTAL 1 #define VERTICAL 2 -void CDxfWrite::writeLinearDim(const double* textMidPoint, const double* lineDefPoint, - const double* extLine1, const double* extLine2, - const char* dimText, int type) +void CDxfWrite::writeLinearDim(const double* textMidPoint, + const double* lineDefPoint, + const double* extLine1, + const double* extLine2, + const char* dimText, + int type) { m_ssEntity << " 0" << std::endl; m_ssEntity << "DIMENSION" << std::endl; @@ -1083,17 +1227,17 @@ void CDxfWrite::writeLinearDim(const double* textMidPoint, const double* lineDef m_ssEntity << 1 << std::endl; // dimType1 = Aligned } if ( (type == HORIZONTAL) || - (type == VERTICAL) ) { + (type == VERTICAL) ) { m_ssEntity << " 70" << std::endl; m_ssEntity << 32 << std::endl; // dimType0 = Aligned + 32 (bit for unique block)? } -// m_ssEntity << " 71" << std::endl; // not R12 -// m_ssEntity << 1 << std::endl; // attachPoint ??1 = topleft + // m_ssEntity << " 71" << std::endl; // not R12 + // m_ssEntity << 1 << std::endl; // attachPoint ??1 = topleft m_ssEntity << " 1" << std::endl; m_ssEntity << dimText << std::endl; m_ssEntity << " 3" << std::endl; m_ssEntity << "STANDARD" << std::endl; //style -//linear dims + //linear dims if (m_version > 12) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDbAlignedDimension" << std::endl; @@ -1115,27 +1259,27 @@ void CDxfWrite::writeLinearDim(const double* textMidPoint, const double* lineDef m_ssEntity << " 50" << std::endl; m_ssEntity << "90" << std::endl; } - if ( (type == HORIZONTAL) || - (type == VERTICAL) ) { + if ( (type == HORIZONTAL) || (type == VERTICAL) ) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDbRotatedDimension" << std::endl; } } writeDimBlockPreamble(); - writeLinearDimBlock(textMidPoint,lineDefPoint, - extLine1, extLine2, - dimText, type); + writeLinearDimBlock(textMidPoint, lineDefPoint, extLine1, extLine2, dimText, type); writeBlockTrailer(); } //*************************** //writeAngularDim //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeAngularDim(const double* textMidPoint, const double* lineDefPoint, - const double* startExt1, const double* endExt1, - const double* startExt2, const double* endExt2, - const char* dimText) +void CDxfWrite::writeAngularDim(const double* textMidPoint, + const double* lineDefPoint, + const double* startExt1, + const double* endExt1, + const double* startExt2, + const double* endExt2, + const char* dimText) { m_ssEntity << " 0" << std::endl; m_ssEntity << "DIMENSION" << std::endl; @@ -1172,14 +1316,14 @@ void CDxfWrite::writeAngularDim(const double* textMidPoint, const double* lineDe m_ssEntity << " 70" << std::endl; m_ssEntity << 2 << std::endl; // dimType 2 = Angular 5 = Angular 3 point - // +32 for block?? (not R12) -// m_ssEntity << " 71" << std::endl; // not R12? not required? -// m_ssEntity << 5 << std::endl; // attachPoint 5 = middle + // +32 for block?? (not R12) + // m_ssEntity << " 71" << std::endl; // not R12? not required? + // m_ssEntity << 5 << std::endl; // attachPoint 5 = middle m_ssEntity << " 1" << std::endl; m_ssEntity << dimText << std::endl; m_ssEntity << " 3" << std::endl; m_ssEntity << "STANDARD" << std::endl; //style -//angular dims + //angular dims if (m_version > 12) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDb2LineAngularDimension" << std::endl; @@ -1212,9 +1356,12 @@ void CDxfWrite::writeAngularDim(const double* textMidPoint, const double* lineDe m_ssEntity << " 36" << std::endl; m_ssEntity << lineDefPoint[2] << std::endl; writeDimBlockPreamble(); - writeAngularDimBlock(textMidPoint, lineDefPoint, - startExt1, endExt1, - startExt2, endExt2, + writeAngularDimBlock(textMidPoint, + lineDefPoint, + startExt1, + endExt1, + startExt2, + endExt2, dimText); writeBlockTrailer(); } @@ -1222,9 +1369,10 @@ void CDxfWrite::writeAngularDim(const double* textMidPoint, const double* lineDe //*************************** //writeRadialDim //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeRadialDim(const double* centerPoint, const double* textMidPoint, - const double* arcPoint, - const char* dimText) +void CDxfWrite::writeRadialDim(const double* centerPoint, + const double* textMidPoint, + const double* arcPoint, + const char* dimText) { m_ssEntity << " 0" << std::endl; m_ssEntity << "DIMENSION" << std::endl; @@ -1258,13 +1406,13 @@ void CDxfWrite::writeRadialDim(const double* centerPoint, const double* textMidP m_ssEntity << textMidPoint[2] << std::endl; m_ssEntity << " 70" << std::endl; m_ssEntity << 4 << std::endl; // dimType 4 = Radius -// m_ssEntity << " 71" << std::endl; // not R12 -// m_ssEntity << 1 << std::endl; // attachPoint 5 = middle center + // m_ssEntity << " 71" << std::endl; // not R12 + // m_ssEntity << 1 << std::endl; // attachPoint 5 = middle center m_ssEntity << " 1" << std::endl; m_ssEntity << dimText << std::endl; m_ssEntity << " 3" << std::endl; m_ssEntity << "STANDARD" << std::endl; //style -//radial dims + //radial dims if (m_version > 12) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDbRadialDimension" << std::endl; @@ -1286,9 +1434,10 @@ void CDxfWrite::writeRadialDim(const double* centerPoint, const double* textMidP //*************************** //writeDiametricDim //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeDiametricDim(const double* textMidPoint, - const double* arcPoint1, const double* arcPoint2, - const char* dimText) +void CDxfWrite::writeDiametricDim(const double* textMidPoint, + const double* arcPoint1, + const double* arcPoint2, + const char* dimText) { m_ssEntity << " 0" << std::endl; m_ssEntity << "DIMENSION" << std::endl; @@ -1322,13 +1471,13 @@ void CDxfWrite::writeDiametricDim(const double* textMidPoint, m_ssEntity << textMidPoint[2] << std::endl; m_ssEntity << " 70" << std::endl; m_ssEntity << 3 << std::endl; // dimType 3 = Diameter -// m_ssEntity << " 71" << std::endl; // not R12 -// m_ssEntity << 5 << std::endl; // attachPoint 5 = middle center + // m_ssEntity << " 71" << std::endl; // not R12 + // m_ssEntity << 5 << std::endl; // attachPoint 5 = middle center m_ssEntity << " 1" << std::endl; m_ssEntity << dimText << std::endl; m_ssEntity << " 3" << std::endl; m_ssEntity << "STANDARD" << std::endl; //style -//diametric dims + //diametric dims if (m_version > 12) { m_ssEntity << "100" << std::endl; m_ssEntity << "AcDbDiametricDimension" << std::endl; @@ -1350,7 +1499,7 @@ void CDxfWrite::writeDiametricDim(const double* textMidPoint, //*************************** //writeDimBlockPreamble //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeDimBlockPreamble(void) +void CDxfWrite::writeDimBlockPreamble() { if (m_version > 12) { std::string blockName("*"); @@ -1395,7 +1544,7 @@ void CDxfWrite::writeDimBlockPreamble(void) //*************************** //writeBlockTrailer //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeBlockTrailer(void) +void CDxfWrite::writeBlockTrailer() { m_ssBlock << " 0" << std::endl; m_ssBlock << "ENDBLK" << std::endl; @@ -1407,8 +1556,8 @@ void CDxfWrite::writeBlockTrailer(void) m_ssBlock << "100" << std::endl; m_ssBlock << "AcDbEntity" << std::endl; } -// m_ssBlock << " 67" << std::endl; -// m_ssBlock << "1" << std::endl; + // m_ssBlock << " 67" << std::endl; + // m_ssBlock << "1" << std::endl; m_ssBlock << " 8" << std::endl; m_ssBlock << getLayerName() << std::endl; if (m_version > 12) { @@ -1420,9 +1569,12 @@ void CDxfWrite::writeBlockTrailer(void) //*************************** //writeLinearDimBlock //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeLinearDimBlock(const double* textMidPoint, const double* dimLine, - const double* e1Start, const double* e2Start, - const char* dimText, int type) +void CDxfWrite::writeLinearDimBlock(const double* textMidPoint, + const double* dimLine, + const double* e1Start, + const double* e2Start, + const char* dimText, + int type) { Base::Vector3d e1S(e1Start[0],e1Start[1],e1Start[2]); Base::Vector3d e2S(e2Start[0],e2Start[1],e2Start[2]); @@ -1436,7 +1588,8 @@ void CDxfWrite::writeLinearDimBlock(const double* textMidPoint, const double* di angle = angle * 180.0 / M_PI; if (type == ALIGNED) { //NOP - } else if (type == HORIZONTAL) { + } + else if (type == HORIZONTAL) { double x = e1Start[0]; double y = dimLine[1]; e1E = Base::Vector3d(x, y, 0.0); @@ -1451,7 +1604,8 @@ void CDxfWrite::writeLinearDimBlock(const double* textMidPoint, const double* di para = Base::Vector3d(-1, 0, 0); //left } angle = 0; - } else if (type == VERTICAL) { + } + else if (type == VERTICAL) { double x = dimLine[0]; double y = e1Start[1]; e1E = Base::Vector3d(x, y, 0.0); @@ -1471,44 +1625,45 @@ void CDxfWrite::writeLinearDimBlock(const double* textMidPoint, const double* di double arrowLen = 5.0; //magic number double arrowWidth = arrowLen/6.0/2.0; //magic number calc! - putLine(e2S, e2E, - m_ssBlock, getBlockHandle(), - m_saveBlkRecordHandle); + putLine(e2S, e2E, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); - putLine(e1S, e1E, - m_ssBlock, getBlockHandle(), - m_saveBlkRecordHandle); + putLine(e1S, e1E, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); - putLine(e1E, e2E, - m_ssBlock, getBlockHandle(), - m_saveBlkRecordHandle); + putLine(e1E, e2E, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); - putText(dimText,toVector3d(textMidPoint), toVector3d(dimLine),3.5,1, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putText(dimText, + toVector3d(textMidPoint), + toVector3d(dimLine), + 3.5, + 1, + m_ssBlock, + getBlockHandle(), + m_saveBlkRecordHandle); perp.Normalize(); para.Normalize(); Base::Vector3d arrowStart = e1E; Base::Vector3d barb1 = arrowStart + perp*arrowWidth - para*arrowLen; Base::Vector3d barb2 = arrowStart - perp*arrowWidth - para*arrowLen; - putArrow(arrowStart, barb1, barb2, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putArrow(arrowStart, barb1, barb2, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); arrowStart = e2E; barb1 = arrowStart + perp*arrowWidth + para*arrowLen; barb2 = arrowStart - perp*arrowWidth + para*arrowLen; - putArrow(arrowStart, barb1, barb2, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putArrow(arrowStart, barb1, barb2, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); } //*************************** //writeAngularDimBlock //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeAngularDimBlock(const double* textMidPoint, const double* lineDefPoint, - const double* startExt1, const double* endExt1, - const double* startExt2, const double* endExt2, - const char* dimText) +void CDxfWrite::writeAngularDimBlock(const double* textMidPoint, + const double* lineDefPoint, + const double* startExt1, + const double* endExt1, + const double* startExt2, + const double* endExt2, + const char* dimText) { Base::Vector3d e1S(startExt1[0],startExt1[1],startExt1[2]); //apex Base::Vector3d e2S(startExt2[0],startExt2[1],startExt2[2]); @@ -1522,10 +1677,10 @@ void CDxfWrite::writeAngularDimBlock(const double* textMidPoint, const double* l double span = fabs(endAngle - startAngle); double offset = span * 0.10; if (startAngle < 0) { - startAngle += 2.0 * M_PI; + startAngle += 2.0 * M_PI; } if (endAngle < 0) { - endAngle += 2.0 * M_PI; + endAngle += 2.0 * M_PI; } Base::Vector3d startOff(cos(startAngle + offset),sin(startAngle + offset),0.0); Base::Vector3d endOff(cos(endAngle - offset),sin(endAngle - offset),0.0); @@ -1547,8 +1702,8 @@ void CDxfWrite::writeAngularDimBlock(const double* textMidPoint, const double* l } m_ssBlock << " 8" << std::endl; m_ssBlock << "0" << std::endl; -// m_ssBlock << " 62" << std::endl; -// m_ssBlock << " 0" << std::endl; + // m_ssBlock << " 62" << std::endl; + // m_ssBlock << " 0" << std::endl; if (m_version > 12) { m_ssBlock << "100" << std::endl; m_ssBlock << "AcDbCircle" << std::endl; @@ -1570,8 +1725,14 @@ void CDxfWrite::writeAngularDimBlock(const double* textMidPoint, const double* l m_ssBlock << " 51" << std::endl; m_ssBlock << endAngle << std::endl; //end angle - putText(dimText,toVector3d(textMidPoint), toVector3d(textMidPoint),3.5,1, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putText(dimText, + toVector3d(textMidPoint), + toVector3d(textMidPoint), + 3.5, + 1, + m_ssBlock, + getBlockHandle(), + m_saveBlkRecordHandle); e1.Normalize(); e2.Normalize(); @@ -1591,28 +1752,36 @@ void CDxfWrite::writeAngularDimBlock(const double* textMidPoint, const double* l Base::Vector3d barb1 = arrow1Start + perp1*arrowWidth - tanP1*arrowLen; Base::Vector3d barb2 = arrow1Start - perp1*arrowWidth - tanP1*arrowLen; - putArrow(arrow1Start, barb1, barb2, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putArrow(arrow1Start, barb1, barb2, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); barb1 = arrow2Start + perp2*arrowWidth - tanP2*arrowLen; barb2 = arrow2Start - perp2*arrowWidth - tanP2*arrowLen; - putArrow(arrow2Start, barb1, barb2, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putArrow(arrow2Start, barb1, barb2, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); } //*************************** //writeRadialDimBlock //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeRadialDimBlock(const double* centerPoint, const double* textMidPoint, - const double* arcPoint, const char* dimText) -{ - putLine(toVector3d(centerPoint), toVector3d(arcPoint), - m_ssBlock, getBlockHandle(), +void CDxfWrite::writeRadialDimBlock(const double* centerPoint, + const double* textMidPoint, + const double* arcPoint, + const char* dimText) +{ + putLine(toVector3d(centerPoint), + toVector3d(arcPoint), + m_ssBlock, + getBlockHandle(), m_saveBlkRecordHandle); - putText(dimText,toVector3d(textMidPoint), toVector3d(textMidPoint),3.5,1, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putText(dimText, + toVector3d(textMidPoint), + toVector3d(textMidPoint), + 3.5, + 1, + m_ssBlock, + getBlockHandle(), + m_saveBlkRecordHandle); Base::Vector3d c(centerPoint[0],centerPoint[1],centerPoint[2]); Base::Vector3d a(arcPoint[0],arcPoint[1],arcPoint[2]); @@ -1625,23 +1794,31 @@ void CDxfWrite::writeRadialDimBlock(const double* centerPoint, const double* tex Base::Vector3d barb1 = arrowStart + perp*arrowWidth - para*arrowLen; Base::Vector3d barb2 = arrowStart - perp*arrowWidth - para*arrowLen; - putArrow(arrowStart, barb1, barb2, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putArrow(arrowStart, barb1, barb2, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); } //*************************** //writeDiametricDimBlock //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeDiametricDimBlock(const double* textMidPoint, - const double* arcPoint1, const double* arcPoint2, - const char* dimText) -{ - putLine(toVector3d(arcPoint1), toVector3d(arcPoint2), - m_ssBlock, getBlockHandle(), +void CDxfWrite::writeDiametricDimBlock(const double* textMidPoint, + const double* arcPoint1, + const double* arcPoint2, + const char* dimText) +{ + putLine(toVector3d(arcPoint1), + toVector3d(arcPoint2), + m_ssBlock, + getBlockHandle(), m_saveBlkRecordHandle); - putText(dimText,toVector3d(textMidPoint), toVector3d(textMidPoint),3.5,1, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putText(dimText, + toVector3d(textMidPoint), + toVector3d(textMidPoint), + 3.5, + 1, + m_ssBlock, + getBlockHandle(), + m_saveBlkRecordHandle); Base::Vector3d a1(arcPoint1[0],arcPoint1[1],arcPoint1[2]); Base::Vector3d a2(arcPoint2[0],arcPoint2[1],arcPoint2[2]); @@ -1654,21 +1831,18 @@ void CDxfWrite::writeDiametricDimBlock(const double* textMidPoint, Base::Vector3d barb1 = arrowStart + perp*arrowWidth + para*arrowLen; Base::Vector3d barb2 = arrowStart - perp*arrowWidth + para*arrowLen; - putArrow(arrowStart, barb1, barb2, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); + putArrow(arrowStart, barb1, barb2, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); arrowStart = a2; barb1 = arrowStart + perp*arrowWidth - para*arrowLen; barb2 = arrowStart - perp*arrowWidth - para*arrowLen; - putArrow(arrowStart, barb1, barb2, - m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); - + putArrow(arrowStart, barb1, barb2, m_ssBlock, getBlockHandle(), m_saveBlkRecordHandle); } //*************************** //writeBlocksSection //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeBlocksSection(void) +void CDxfWrite::writeBlocksSection() { if (m_version < 14) { std::stringstream ss; @@ -1687,7 +1861,7 @@ void CDxfWrite::writeBlocksSection(void) //*************************** //writeEntitiesSection //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeEntitiesSection(void) +void CDxfWrite::writeEntitiesSection() { std::stringstream ss; ss << "entities" << m_version << ".rub"; @@ -1705,7 +1879,7 @@ void CDxfWrite::writeEntitiesSection(void) //*************************** //writeObjectsSection //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project -void CDxfWrite::writeObjectsSection(void) +void CDxfWrite::writeObjectsSection() { if (m_version < 14) { return; @@ -1720,17 +1894,15 @@ CDxfRead::CDxfRead(const char* filepath) : m_ifs(filepath) { // start the file - memset( m_str, '\0', sizeof(m_str) ); - memset( m_unused_line, '\0', sizeof(m_unused_line) ); m_fail = false; - m_aci = 0; + m_ColorIndex = 0; m_eUnits = eMillimeters; m_measurement_inch = false; - safe_strcpy(m_layer_name, "0"); // Default layer name - memset( m_section_name, '\0', sizeof(m_section_name) ); - memset( m_block_name, '\0', sizeof(m_block_name) ); + m_layer_name = "0"; // Default layer name m_ignore_errors = true; + m_version = RUnknown; + if (!m_ifs) m_fail = true; else @@ -1741,136 +1913,130 @@ CDxfRead::~CDxfRead() { } -double CDxfRead::mm( double value ) const +double CDxfRead::mm(double value) const { - if(m_measurement_inch) - { + // re #6461 + // this if handles situation of malformed DXF file where + // MEASUREMENT specifies English units, but + // INSUNITS specifies millimeters or is not specified + //(millimeters is our default) + if (m_measurement_inch && (m_eUnits == eMillimeters)) { value *= 25.4; } - switch(m_eUnits) - { - case eUnspecified: return(value); // We don't know any better. - case eInches: return(value * 25.4); - case eFeet: return(value * 25.4 * 12); - case eMiles: return(value * 1609344.0); - case eMillimeters: return(value); - case eCentimeters: return(value * 10.0); - case eMeters: return(value * 1000.0); - case eKilometers: return(value * 1000000.0); - case eMicroinches: return(value * 25.4 / 1000.0); - case eMils: return(value * 25.4 / 1000.0); - case eYards: return(value * 3 * 12 * 25.4); - case eAngstroms: return(value * 0.0000001); - case eNanometers: return(value * 0.000001); - case eMicrons: return(value * 0.001); - case eDecimeters: return(value * 100.0); - case eDekameters: return(value * 10000.0); - case eHectometers: return(value * 100000.0); - case eGigameters: return(value * 1000000000000.0); - case eAstronomicalUnits: return(value * 149597870690000.0); - case eLightYears: return(value * 9454254955500000000.0); - case eParsecs: return(value * 30856774879000000000.0); - default: return(value); // We don't know any better. + switch (m_eUnits) { + case eUnspecified: + return (value * 1.0); // We don't know any better. + case eInches: + return (value * 25.4); + case eFeet: + return (value * 25.4 * 12); + case eMiles: + return (value * 1609344.0); + case eMillimeters: + return (value * 1.0); + case eCentimeters: + return (value * 10.0); + case eMeters: + return (value * 1000.0); + case eKilometers: + return (value * 1000000.0); + case eMicroinches: + return (value * 25.4 / 1000.0); + case eMils: + return (value * 25.4 / 1000.0); + case eYards: + return (value * 3 * 12 * 25.4); + case eAngstroms: + return (value * 0.0000001); + case eNanometers: + return (value * 0.000001); + case eMicrons: + return (value * 0.001); + case eDecimeters: + return (value * 100.0); + case eDekameters: + return (value * 10000.0); + case eHectometers: + return (value * 100000.0); + case eGigameters: + return (value * 1000000000000.0); + case eAstronomicalUnits: + return (value * 149597870690000.0); + case eLightYears: + return (value * 9454254955500000000.0); + case eParsecs: + return (value * 30856774879000000000.0); + default: + return (value * 1.0); // We don't know any better. } // End switch } // End mm() method +const Dxf_STYLE* CDxfRead::findStyle(const std::string& name) const +{ + if (name.empty()) + return nullptr; + + auto itFound = m_mapStyle.find(name); + return itFound != m_mapStyle.cend() ? &itFound->second : nullptr; +} bool CDxfRead::ReadLine() { - double s[3] = {0, 0, 0}; - double e[3] = {0, 0, 0}; + DxfCoords s = {}; + DxfCoords e = {}; bool hidden = false; - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found, so finish with line + ResolveColorIndex(); + OnReadLine(s, e, hidden); + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadLine()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found, so finish with line - DerefACI(); - OnReadLine(s, e, hidden); - hidden = false; - return true; - - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - - case 6: // line style name follows - get_line(); - if(m_str[0] == 'h' || m_str[0] == 'H')hidden = true; - break; - - case 10: - // start x - get_line(); - ss.str(m_str); ss >> s[0]; s[0] = mm(s[0]); if(ss.fail()) return false; - break; - case 20: - // start y - get_line(); - ss.str(m_str); ss >> s[1]; s[1] = mm(s[1]); if(ss.fail()) return false; - break; - case 30: - // start z - get_line(); - ss.str(m_str); ss >> s[2]; s[2] = mm(s[2]); if(ss.fail()) return false; - break; - case 11: - // end x - get_line(); - ss.str(m_str); ss >> e[0]; e[0] = mm(e[0]); if(ss.fail()) return false; - break; - case 21: - // end y - get_line(); - ss.str(m_str); ss >> e[1]; e[1] = mm(e[1]); if(ss.fail()) return false; - break; - case 31: - // end z - get_line(); - ss.str(m_str); ss >> e[2]; e[2] = mm(e[2]); if(ss.fail()) return false; - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + get_line(); + switch (n) { + case 6: // line style name follows + if (!m_str.empty() && (m_str[0] == 'h' || m_str[0] == 'H')) { + hidden = true; + } + break; + case 10: case 20: case 30: + // start coords + HandleCoordCode(n, &s); + break; + case 11: case 21: case 31: + // end coords + HandleCoordCode<11, 21, 31>(n, &e); + break; + case 100: + case 39: + case 210: + case 220: + case 230: + // skip the next line + break; + default: + HandleCommonGroupCode(n); + break; } } try { - DerefACI(); + ResolveColorIndex(); OnReadLine(s, e, false); } - catch(...) - { - if (! IgnoreErrors()) throw; // Re-throw the exception. + catch (...) { + if (!IgnoreErrors()) { + throw; // Re-throw the exception. + } } return false; @@ -1878,78 +2044,52 @@ bool CDxfRead::ReadLine() bool CDxfRead::ReadPoint() { - double s[3] = {0, 0, 0}; + DxfCoords s = {}; - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found, so finish with line + ResolveColorIndex(); + OnReadPoint(s); + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadPoint()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found, so finish with line - DerefACI(); - OnReadPoint(s); - return true; - - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - - case 10: - // start x - get_line(); - ss.str(m_str); ss >> s[0]; s[0] = mm(s[0]); if(ss.fail()) return false; - break; - case 20: - // start y - get_line(); - ss.str(m_str); ss >> s[1]; s[1] = mm(s[1]); if(ss.fail()) return false; - break; - case 30: - // start z - get_line(); - ss.str(m_str); ss >> s[2]; s[2] = mm(s[2]); if(ss.fail()) return false; - break; - - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; + get_line(); + switch (n){ + case 10: + case 20: + case 30: + // start coords + HandleCoordCode(n, &s); + break; - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + case 100: + case 39: + case 210: + case 220: + case 230: + // skip the next line + break; + default: + HandleCommonGroupCode(n); + break; } - } try { - DerefACI(); + ResolveColorIndex(); OnReadPoint(s); } - catch(...) - { - if (! IgnoreErrors()) throw; // Re-throw the exception. + catch (...) { + if (!IgnoreErrors()) { + throw; // Re-throw the exception. + } } return false; @@ -1960,98 +2100,70 @@ bool CDxfRead::ReadArc() double start_angle = 0.0;// in degrees double end_angle = 0.0; double radius = 0.0; - double c[3] = {0,0,0}; // centre + DxfCoords c = {}; // centre double z_extrusion_dir = 1.0; bool hidden = false; - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found, so finish with arc + ResolveColorIndex(); + OnReadArc(start_angle, end_angle, radius, c, z_extrusion_dir, hidden); + hidden = false; + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadArc()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found, so finish with arc - DerefACI(); - OnReadArc(start_angle, end_angle, radius, c,z_extrusion_dir, hidden); - hidden = false; - return true; - - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - - case 6: // line style name follows - get_line(); - if(m_str[0] == 'h' || m_str[0] == 'H')hidden = true; - break; - - case 10: - // centre x - get_line(); - ss.str(m_str); ss >> c[0]; c[0] = mm(c[0]); if(ss.fail()) return false; - break; - case 20: - // centre y - get_line(); - ss.str(m_str); ss >> c[1]; c[1] = mm(c[1]); if(ss.fail()) return false; - break; - case 30: - // centre z - get_line(); - ss.str(m_str); ss >> c[2]; c[2] = mm(c[2]); if(ss.fail()) return false; - break; - case 40: - // radius - get_line(); - ss.str(m_str); ss >> radius; radius = mm(radius); if(ss.fail()) return false; - break; - case 50: - // start angle - get_line(); - ss.str(m_str); ss >> start_angle; if(ss.fail()) return false; - break; - case 51: - // end angle - get_line(); - ss.str(m_str); ss >> end_angle; if(ss.fail()) return false; - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - + get_line(); + switch (n){ + case 6: // line style name follows + if (!m_str.empty() && (m_str[0] == 'h' || m_str[0] == 'H')) { + hidden = true; + } + break; + case 10: + case 20: + case 30: + // centre coords + HandleCoordCode(n, &c); + break; + case 40: + // radius + radius = mm(stringToDouble(m_str)); + break; + case 50: + // start angle + start_angle = mm(stringToDouble(m_str)); + break; + case 51: + // end angle + end_angle = mm(stringToDouble(m_str)); + break; - case 100: - case 39: - case 210: - case 220: - // skip the next line - get_line(); - break; - case 230: - //Z extrusion direction for arc - get_line(); - ss.str(m_str); ss >> z_extrusion_dir; if(ss.fail()) return false; - break; + case 100: + case 39: + case 210: + case 220: + // skip the next line + break; + case 230: + //Z extrusion direction for arc + z_extrusion_dir = mm(stringToDouble(m_str)); + break; - default: - // skip the next line - get_line(); - break; + default: + HandleCommonGroupCode(n); + break; } } - DerefACI(); + + ResolveColorIndex(); OnReadArc(start_angle, end_angle, radius, c, z_extrusion_dir, false); return false; } @@ -2059,180 +2171,123 @@ bool CDxfRead::ReadArc() bool CDxfRead::ReadSpline() { struct SplineData sd; - sd.norm[0] = 0; - sd.norm[1] = 0; - sd.norm[2] = 1; + sd.norm = {0., 0., 1.}; sd.degree = 0; sd.knots = 0; sd.flag = 0; sd.control_points = 0; sd.fit_points = 0; - double temp_double; - - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found, so finish with Spline + ResolveColorIndex(); + OnReadSpline(sd); + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadSpline()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found, so finish with Spline - DerefACI(); - OnReadSpline(sd); - return true; - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - case 210: - // normal x - get_line(); - ss.str(m_str); ss >> sd.norm[0]; if(ss.fail()) return false; - break; - case 220: - // normal y - get_line(); - ss.str(m_str); ss >> sd.norm[1]; if(ss.fail()) return false; - break; - case 230: - // normal z - get_line(); - ss.str(m_str); ss >> sd.norm[2]; if(ss.fail()) return false; - break; - case 70: - // flag - get_line(); - ss.str(m_str); ss >> sd.flag; if(ss.fail()) return false; - break; - case 71: - // degree - get_line(); - ss.str(m_str); ss >> sd.degree; if(ss.fail()) return false; - break; - case 72: - // knots - get_line(); - ss.str(m_str); ss >> sd.knots; if(ss.fail()) return false; - break; - case 73: - // control points - get_line(); - ss.str(m_str); ss >> sd.control_points; if(ss.fail()) return false; - break; - case 74: - // fit points - get_line(); - ss.str(m_str); ss >> sd.fit_points; if(ss.fail()) return false; - break; - case 12: - // starttan x - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.starttanx.push_back(temp_double); - break; - case 22: - // starttan y - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.starttany.push_back(temp_double); - break; - case 32: - // starttan z - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.starttanz.push_back(temp_double); - break; - case 13: - // endtan x - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.endtanx.push_back(temp_double); - break; - case 23: - // endtan y - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.endtany.push_back(temp_double); - break; - case 33: - // endtan z - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.endtanz.push_back(temp_double); - break; - case 40: - // knot - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.knot.push_back(temp_double); - break; - case 41: - // weight - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.weight.push_back(temp_double); - break; - case 10: - // control x - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.controlx.push_back(temp_double); - break; - case 20: - // control y - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.controly.push_back(temp_double); - break; - case 30: - // control z - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.controlz.push_back(temp_double); - break; - case 11: - // fit x - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.fitx.push_back(temp_double); - break; - case 21: - // fit y - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.fity.push_back(temp_double); - break; - case 31: - // fit z - get_line(); - ss.str(m_str); ss >> temp_double; temp_double = mm(temp_double); if(ss.fail()) return false; - sd.fitz.push_back(temp_double); - break; - case 42: - case 43: - case 44: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + + get_line(); + switch (n) { + case 210: + case 220: + case 230: + // normal coords + HandleCoordCode<210, 220, 230>(n, &sd.norm); + break; + case 70: + // flag + sd.flag = stringToInt(m_str); + break; + case 71: + // degree + sd.degree = stringToInt(m_str); + break; + case 72: + // knots + sd.knots = stringToInt(m_str); + break; + case 73: + // control points + sd.control_points = stringToInt(m_str); + break; + case 74: + // fit points + sd.fit_points = stringToInt(m_str); + break; + case 12: + // starttan x + sd.starttanx.push_back(mm(stringToDouble(m_str))); + break; + case 22: + // starttan y + sd.starttany.push_back(mm(stringToDouble(m_str))); + break; + case 32: + // starttan z + sd.starttanz.push_back(mm(stringToDouble(m_str))); + break; + case 13: + // endtan x + sd.endtanx.push_back(mm(stringToDouble(m_str))); + break; + case 23: + // endtan y + sd.endtany.push_back(mm(stringToDouble(m_str))); + break; + case 33: + // endtan z + sd.endtanz.push_back(mm(stringToDouble(m_str))); + break; + case 40: + // knot + sd.knot.push_back(mm(stringToDouble(m_str))); + break; + case 41: + // weight + sd.weight.push_back(mm(stringToDouble(m_str))); + break; + case 10: + // control x + sd.controlx.push_back(mm(stringToDouble(m_str))); + break; + case 20: + // control y + sd.controly.push_back(mm(stringToDouble(m_str))); + break; + case 30: + // control z + sd.controlz.push_back(mm(stringToDouble(m_str))); + break; + case 11: + // fit x + sd.fitx.push_back(mm(stringToDouble(m_str))); + break; + case 21: + // fit y + sd.fity.push_back(mm(stringToDouble(m_str))); + break; + case 31: + // fit z + sd.fitz.push_back(mm(stringToDouble(m_str))); + break; + case 42: + case 43: + case 44: + // skip the next line + break; + default: + HandleCommonGroupCode(n); + break; } } - DerefACI(); + + ResolveColorIndex(); OnReadSpline(sd); return false; } @@ -2241,271 +2296,341 @@ bool CDxfRead::ReadSpline() bool CDxfRead::ReadCircle() { double radius = 0.0; - double c[3] = {0,0,0}; // centre + DxfCoords c = {}; // centre bool hidden = false; - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found, so finish with Circle + ResolveColorIndex(); + OnReadCircle(c, radius, hidden); + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadCircle()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found, so finish with Circle - DerefACI(); - OnReadCircle(c, radius, hidden); - hidden = false; - return true; - - case 6: // line style name follows - get_line(); - if(m_str[0] == 'h' || m_str[0] == 'H')hidden = true; - break; - - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - case 10: - // centre x - get_line(); - ss.str(m_str); ss >> c[0]; c[0] = mm(c[0]); if(ss.fail()) return false; - break; - case 20: - // centre y - get_line(); - ss.str(m_str); ss >> c[1]; c[1] = mm(c[1]); if(ss.fail()) return false; - break; - case 30: - // centre z - get_line(); - ss.str(m_str); ss >> c[2]; c[2] = mm(c[2]); if(ss.fail()) return false; - break; - case 40: - // radius - get_line(); - ss.str(m_str); ss >> radius; radius = mm(radius); if(ss.fail()) return false; - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; + get_line(); + switch (n){ + case 6: // line style name follows + if (!m_str.empty() && (m_str[0] == 'h' || m_str[0] == 'H')) { + hidden = true; + } + break; + case 10: case 20: case 30: + // centre coords + HandleCoordCode(n, &c); + break; + case 40: + // radius + radius = mm(stringToDouble(m_str)); + break; - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + case 100: + case 39: + case 210: + case 220: + case 230: + // skip the next line + break; + default: + HandleCommonGroupCode(n); + break; } } - DerefACI(); + + ResolveColorIndex(); OnReadCircle(c, radius, false); return false; } - -bool CDxfRead::ReadText() +bool CDxfRead::ReadMText() { - double c[3]; // coordinate - double height = 0.03082; - double rotation = 0.; // degrees - char text[1024] = {}; + Dxf_MTEXT text; + bool withinAcadColumnInfo = false; + bool withinAcadColumns = false; + bool withinAcadDefinedHeight = false; + + while (!m_ifs.eof()) { + get_line(); + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + ResolveColorIndex(); + // Replace \P by \n + size_t pos = text.str.find("\\P", 0); + while (pos != std::string::npos) { + text.str.replace(pos, 2, "\n"); + pos = text.str.find("\\P", pos + 1); + } - memset( c, 0, sizeof(c) ); + text.str = this->toUtf8(text.str); + OnReadMText(text); + return true; + } - while(!m_ifs.eof()) - { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { - this->ReportError_readInteger("DXF::ReadText()"); + + auto fnMatchExtensionBegin = [=](std::string_view extName, bool& tag) { + if (!tag && m_str == extName) { + tag = true; + return true; + } + return false; + }; + + auto fnMatchExtensionEnd = [=](std::string_view extName, bool& tag) { + if (tag && m_str == extName) { + tag = false; + return true; + } return false; + }; + + if (fnMatchExtensionBegin("ACAD_MTEXT_COLUMN_INFO_BEGIN", withinAcadColumnInfo)) { + text.acadHasColumnInfo = true; + continue; // Skip + } + + if (fnMatchExtensionEnd("ACAD_MTEXT_COLUMN_INFO_END", withinAcadColumnInfo)) + continue; // Skip + + if (fnMatchExtensionBegin("ACAD_MTEXT_COLUMNS_BEGIN", withinAcadColumns)) + continue; // Skip + + if (fnMatchExtensionEnd("ACAD_MTEXT_COLUMNS_END", withinAcadColumns)) + continue; // Skip + + if (fnMatchExtensionBegin("ACAD_MTEXT_DEFINED_HEIGHT_BEGIN", withinAcadDefinedHeight)) { + text.acadHasDefinedHeight = true; + continue; // Skip } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - DerefACI(); - OnReadText(c, height * 25.4 / 72.0, rotation, text); - return(true); - - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - case 10: - // centre x - get_line(); - ss.str(m_str); ss >> c[0]; c[0] = mm(c[0]); if(ss.fail()) return false; + if (fnMatchExtensionEnd("ACAD_MTEXT_DEFINED_HEIGHT_END", withinAcadDefinedHeight)) + continue; // Skip + + if (withinAcadColumnInfo) { + // 1040/1070 extended data code was found at beginning of current iteration + const int xn = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + get_line(); // Skip 1040/1070 line + get_line(); // Get value line of extended data code + switch (xn) { + case 75: { // 1070 + const int t = stringToInt(m_str); + if (0 <= t && t <= 2) + text.acadColumnInfo_Type = static_cast(t); + } break; - case 20: - // centre y - get_line(); - ss.str(m_str); ss >> c[1]; c[1] = mm(c[1]); if(ss.fail()) return false; + case 76: // 1070 + text.acadColumnInfo_Count = stringToInt(m_str); break; - case 30: - // centre z - get_line(); - ss.str(m_str); ss >> c[2]; c[2] = mm(c[2]); if(ss.fail()) return false; + case 78: // 1070 + text.acadColumnInfo_FlowReversed = stringToInt(m_str) != 0; break; - case 40: - // text height - get_line(); - ss.str(m_str); ss >> height; height = mm(height); if(ss.fail()) return false; + case 79: // 1070 + text.acadColumnInfo_AutoHeight = stringToInt(m_str) != 0; break; - case 1: - // text - get_line(); - safe_strcpy(text, m_str); + case 48: // 1040 + text.acadColumnInfo_Width = mm(stringToDouble(m_str)); break; - - case 50: - // text rotation - get_line(); - ss.str(m_str); ss >> rotation; if(ss.fail()) return false; + case 49: // 1040 + text.acadColumnInfo_GutterWidth = mm(stringToDouble(m_str)); break; + } // endswitch - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; + continue; // Skip + } - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + if (withinAcadDefinedHeight) { + // 1040/1070 extended data code was found at beginning of current iteration + const int xn = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + get_line(); // Skip 1040/1070 line + get_line(); // Get value line of extended data code + if (xn == 46) + text.acadDefinedHeight = mm(stringToDouble(m_str)); + + continue; // Skip + } + + switch (n) { + case 10: case 20: case 30: + // centre coords + HandleCoordCode(n, &text.insertionPoint); + break; + case 40: + // text height + text.height = mm(stringToDouble(m_str)); + break; + case 50: + // text rotation + text.rotationAngle = stringToDouble(m_str); + break; + case 3: + // Additional text that goes before the type 1 text + // Note that if breaking the text into type-3 records splits a UFT-8 encoding we do + // the decoding after splicing the lines together. I'm not sure if this actually + // occurs, but handling the text this way will treat this condition properly. + text.str.append(m_str); + break; + case 1: + // final text + text.str.append(m_str); + break; + + case 71: { + // attachment point + const int ap = stringToInt(m_str); + if (ap >= 1 && ap <= 9) + text.attachmentPoint = static_cast(ap); + } + break; + + case 11: case 21: case 31: + // X-axis direction vector + HandleCoordCode<11, 21, 31>(n, &text.xAxisDirection); + break; + + case 210: case 220: case 230: + // extrusion direction + HandleCoordCode<210, 220, 230>(n, &text.extrusionDirection); + break; + + default: + HandleCommonGroupCode(n); + break; } } return false; } +bool CDxfRead::ReadText() +{ + Dxf_TEXT text; + + while (!m_ifs.eof()) { + get_line(); + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + ResolveColorIndex(); + OnReadText(text); + return true; + } + + get_line(); + switch (n) { + case 10: case 20: case 30: + HandleCoordCode(n, &text.firstAlignmentPoint); + break; + case 40: + text.height = mm(stringToDouble(m_str)); + break; + case 1: + text.str = this->toUtf8(m_str); + break; + case 50: + text.rotationAngle = stringToDouble(m_str); + break; + case 41: + text.relativeXScaleFactorWidth = stringToDouble(m_str); + break; + case 51: + text.obliqueAngle = stringToDouble(m_str); + break; + case 7: + text.styleName = m_str; + break; + case 72: { + const int hjust = stringToInt(m_str); + if (hjust >= 0 && hjust <= 5) + text.horizontalJustification = static_cast(hjust); + } + break; + case 11: case 21: case 31: + HandleCoordCode<11, 21, 31>(n, &text.secondAlignmentPoint); + break; + case 210: case 220: case 230: + HandleCoordCode<210, 220, 230>(n, &text.extrusionDirection); + break; + case 73: { + const int vjust = stringToInt(m_str); + if (vjust >= 0 && vjust <= 3) + text.verticalJustification = static_cast(vjust); + } + break; + default: + HandleCommonGroupCode(n); + break; + } + } + + return false; +} bool CDxfRead::ReadEllipse() { - double c[3] = {0,0,0}; // centre - double m[3] = {0,0,0}; //major axis point - double ratio=0; //ratio of major to minor axis - double start=0; //start of arc - double end=0; // end of arc + DxfCoords c = {}; // centre + DxfCoords m = {}; //major axis point + double ratio = 0; //ratio of major to minor axis + double start = 0; //start of arc + double end = 0; // end of arc - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found, so finish with Ellipse + ResolveColorIndex(); + OnReadEllipse(c, m, ratio, start, end); + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadEllipse()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found, so finish with Ellipse - DerefACI(); - OnReadEllipse(c, m, ratio, start, end); - return true; - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - case 10: - // centre x - get_line(); - ss.str(m_str); ss >> c[0]; c[0] = mm(c[0]); if(ss.fail()) return false; - break; - case 20: - // centre y - get_line(); - ss.str(m_str); ss >> c[1]; c[1] = mm(c[1]); if(ss.fail()) return false; - break; - case 30: - // centre z - get_line(); - ss.str(m_str); ss >> c[2]; c[2] = mm(c[2]); if(ss.fail()) return false; - break; - case 11: - // major x - get_line(); - ss.str(m_str); ss >> m[0]; m[0] = mm(m[0]); if(ss.fail()) return false; - break; - case 21: - // major y - get_line(); - ss.str(m_str); ss >> m[1]; m[1] = mm(m[1]); if(ss.fail()) return false; - break; - case 31: - // major z - get_line(); - ss.str(m_str); ss >> m[2]; m[2] = mm(m[2]); if(ss.fail()) return false; - break; - case 40: - // ratio - get_line(); - ss.str(m_str); ss >> ratio; if(ss.fail()) return false; - break; - case 41: - // start - get_line(); - ss.str(m_str); ss >> start; if(ss.fail()) return false; - break; - case 42: - // end - get_line(); - ss.str(m_str); ss >> end; if(ss.fail()) return false; - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - case 100: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + get_line(); + switch (n) { + case 10: case 20: case 30: + // centre coords + HandleCoordCode(n, &c); + break; + case 11: case 21: case 31: + // major coords + HandleCoordCode<11, 21, 31>(n, &m); + break; + case 40: + // ratio + ratio = stringToDouble(m_str); + break; + case 41: + // start + start = stringToDouble(m_str); + break; + case 42: + // end + end = stringToDouble(m_str); + break; + case 100: + case 210: + case 220: + case 230: + // skip the next line + break; + default: + HandleCommonGroupCode(n); + break; } } - DerefACI(); + + ResolveColorIndex(); OnReadEllipse(c, m, ratio, start, end); return false; } - +// TODO Remove this(refactoring of CDxfRead::ReadLwPolyLine() static bool poly_prev_found = false; static double poly_prev_x; static double poly_prev_y; @@ -2517,29 +2642,28 @@ static double poly_first_x; static double poly_first_y; static double poly_first_z; -static void AddPolyLinePoint(CDxfRead* dxf_read, double x, double y, double z, bool bulge_found, double bulge) +// TODO Remove this(refactoring of CDxfRead::ReadLwPolyLine() +static void +AddPolyLinePoint(CDxfRead* dxf_read, double x, double y, double z, bool bulge_found, double bulge) { try { - if(poly_prev_found) - { + if (poly_prev_found) { bool arc_done = false; - if(poly_prev_bulge_found) - { + if (poly_prev_bulge_found) { double cot = 0.5 * ((1.0 / poly_prev_bulge) - poly_prev_bulge); double cx = ((poly_prev_x + x) - ((y - poly_prev_y) * cot)) / 2.0; double cy = ((poly_prev_y + y) + ((x - poly_prev_x) * cot)) / 2.0; - double ps[3] = {poly_prev_x, poly_prev_y, poly_prev_z}; - double pe[3] = {x, y, z}; - double pc[3] = {cx, cy, (poly_prev_z + z)/2.0}; + const DxfCoords ps = {poly_prev_x, poly_prev_y, poly_prev_z}; + const DxfCoords pe = {x, y, z}; + const DxfCoords pc = {cx, cy, (poly_prev_z + z)/2.0}; dxf_read->OnReadArc(ps, pe, pc, poly_prev_bulge >= 0, false); arc_done = true; } - if(!arc_done) - { - double s[3] = {poly_prev_x, poly_prev_y, poly_prev_z}; - double e[3] = {x, y, z}; + if (!arc_done) { + const DxfCoords s = {poly_prev_x, poly_prev_y, poly_prev_z}; + const DxfCoords e = {x, y, z}; dxf_read->OnReadLine(s, e, false); } } @@ -2548,8 +2672,7 @@ static void AddPolyLinePoint(CDxfRead* dxf_read, double x, double y, double z, b poly_prev_x = x; poly_prev_y = y; poly_prev_z = z; - if(!poly_first_found) - { + if (!poly_first_found) { poly_first_x = x; poly_first_y = y; poly_first_z = z; @@ -2558,18 +2681,21 @@ static void AddPolyLinePoint(CDxfRead* dxf_read, double x, double y, double z, b poly_prev_bulge_found = bulge_found; poly_prev_bulge = bulge; } - catch(...) - { - if (! dxf_read->IgnoreErrors()) throw; // Re-throw it. + catch (...) { + if (!dxf_read->IgnoreErrors()) { + throw; // Re-throw it. + } } } +// TODO Remove this(refactoring of CDxfRead::ReadLwPolyLine() static void PolyLineStart() { poly_prev_found = false; poly_first_found = false; } +// TODO Reimplement this function(refactoring of CDxfRead::ReadLwPolyLine() bool CDxfRead::ReadLwPolyLine() { PolyLineStart(); @@ -2585,89 +2711,95 @@ bool CDxfRead::ReadLwPolyLine() int flags; bool next_item_found = false; - while(!m_ifs.eof() && !next_item_found) - { + while (!m_ifs.eof() && !next_item_found) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str); + if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadLwPolyLine()"); return false; } + std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found - - DerefACI(); - if(x_found && y_found){ - // add point - AddPolyLinePoint(this, x, y, z, bulge_found, bulge); - bulge_found = false; - x_found = false; - y_found = false; - } - next_item_found = true; - break; - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - case 10: - // x - get_line(); - if(x_found && y_found){ - // add point - AddPolyLinePoint(this, x, y, z, bulge_found, bulge); - bulge_found = false; - x_found = false; - y_found = false; - } - ss.str(m_str); ss >> x; x = mm(x); if(ss.fail()) return false; - x_found = true; - break; - case 20: - // y - get_line(); - ss.str(m_str); ss >> y; y = mm(y); if(ss.fail()) return false; - y_found = true; - break; - case 38: - // elevation - get_line(); - ss.str(m_str); ss >> z; z = mm(z); if(ss.fail()) return false; - break; - case 42: - // bulge - get_line(); - ss.str(m_str); ss >> bulge; if(ss.fail()) return false; - bulge_found = true; - break; - case 70: - // flags - get_line(); - if(sscanf(m_str, "%d", &flags) != 1)return false; - closed = ((flags & 1) != 0); - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - default: - // skip the next line - get_line(); - break; + ss.imbue(std::locale::classic()); + switch (n){ + case 0: + // next item found + ResolveColorIndex(); + if (x_found && y_found){ + // add point + AddPolyLinePoint(this, x, y, z, bulge_found, bulge); + bulge_found = false; + x_found = false; + y_found = false; + } + next_item_found = true; + break; + case 10: + // x + get_line(); + if (x_found && y_found) { + // add point + AddPolyLinePoint(this, x, y, z, bulge_found, bulge); + bulge_found = false; + x_found = false; + y_found = false; + } + ss.str(m_str); + ss >> x; + x = mm(x); + if (ss.fail()) { + return false; + } + x_found = true; + break; + case 20: + // y + get_line(); + ss.str(m_str); + ss >> y; + y = mm(y); + if (ss.fail()) { + return false; + } + y_found = true; + break; + case 38: + // elevation + get_line(); + ss.str(m_str); + ss >> z; + z = mm(z); + if (ss.fail()) { + return false; + } + break; + case 42: + // bulge + get_line(); + ss.str(m_str); + ss >> bulge; + if (ss.fail()) { + return false; + } + bulge_found = true; + break; + case 70: + // flags + get_line(); + flags = stringToInt(m_str); + closed = ((flags & 1) != 0); + break; + default: + get_line(); + HandleCommonGroupCode(n); + break; } } - if(next_item_found) - { - if(closed && poly_first_found) - { + if (next_item_found) { + if (closed && poly_first_found) { // repeat the first point - DerefACI(); + ResolveColorIndex(); AddPolyLinePoint(this, poly_first_x, poly_first_y, poly_first_z, false, 0.0); } return true; @@ -2676,75 +2808,93 @@ bool CDxfRead::ReadLwPolyLine() return false; } - -bool CDxfRead::ReadVertex(double *pVertex, bool *bulge_found, double *bulge) +bool CDxfRead::ReadVertex(Dxf_VERTEX* vertex) { bool x_found = false; bool y_found = false; - - double x = 0.0; - double y = 0.0; - double z = 0.0; - *bulge = 0.0; - *bulge_found = false; - - pVertex[0] = 0.0; - pVertex[1] = 0.0; - pVertex[2] = 0.0; - - while(!m_ifs.eof()) { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + ResolveColorIndex(); + put_line(m_str); // read one line too many. put it back. + return x_found && y_found; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadVertex()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - DerefACI(); - put_line(m_str); // read one line too many. put it back. - return(x_found && y_found); - break; - case 8: // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); + get_line(); + switch (n){ + case 10: case 20: case 30: + // coords + x_found = x_found || n == 10; + y_found = y_found || n == 20; + HandleCoordCode(n, &vertex->point); break; - - case 10: - // x - get_line(); - ss.str(m_str); ss >> x; pVertex[0] = mm(x); if(ss.fail()) return false; - x_found = true; + case 42: { + // bulge + const int bulge = stringToInt(m_str); + if (bulge == 0) + vertex->bulge = Dxf_VERTEX::Bulge::StraightSegment; + else + vertex->bulge = Dxf_VERTEX::Bulge::SemiCircle; + } break; - case 20: - // y - get_line(); - ss.str(m_str); ss >> y; pVertex[1] = mm(y); if(ss.fail()) return false; - y_found = true; + case 70: + // flags + vertex->flags = stringToUnsigned(m_str); break; - case 30: - // z - get_line(); - ss.str(m_str); ss >> z; pVertex[2] = mm(z); if(ss.fail()) return false; + default: + HandleCommonGroupCode(n); break; + } + } - case 42: - get_line(); - *bulge_found = true; - ss.str(m_str); ss >> *bulge; if(ss.fail()) return false; - break; - case 62: - // color index + return false; +} + +bool CDxfRead::ReadSolid() +{ + Dxf_SOLID solid; + + while (!m_ifs.eof()) { get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + ResolveColorIndex(); + OnReadSolid(solid); + return true; + } + else if (isStringToErrorValue(n)) { + this->ReportError_readInteger("DXF::ReadSolid()"); + return false; + } + get_line(); + switch (n) { + case 10: case 20: case 30: + HandleCoordCode<10, 20, 30>(n, &solid.corner1); + break; + case 11: case 21: case 31: + HandleCoordCode<11, 21, 31>(n, &solid.corner2); + break; + case 12: case 22: case 32: + HandleCoordCode<12, 22, 32>(n, &solid.corner3); + break; + case 13: case 23: case 33: + HandleCoordCode<13, 23, 33>(n, &solid.corner4); + solid.hasCorner4 = true; + break; + case 39: + solid.thickness = stringToDouble(m_str); + break; + case 210: case 220: case 230: + HandleCoordCode<210, 220, 230>(n, &solid.extrusionDirection); + break; default: - // skip the next line - get_line(); + HandleCommonGroupCode(n); break; } } @@ -2752,658 +2902,615 @@ bool CDxfRead::ReadVertex(double *pVertex, bool *bulge_found, double *bulge) return false; } +bool CDxfRead::ReadSection() +{ + m_section_name.clear(); + get_line(); + get_line(); + if (m_str != "ENTITIES") + m_section_name = m_str; + m_block_name.clear(); + return true; +} -bool CDxfRead::ReadPolyLine() +bool CDxfRead::ReadTable() { - PolyLineStart(); + get_line(); + get_line(); + return true; +} - bool closed = false; - int flags; - bool first_vertex_section_found = false; - double first_vertex[3] = {0,0,0}; - bool bulge_found; - double bulge; +bool CDxfRead::ReadEndSec() +{ + m_section_name.clear(); + m_block_name.clear(); + return true; +} - while(!m_ifs.eof()) - { +bool CDxfRead::ReadPolyLine() +{ + Dxf_POLYLINE polyline; + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadPolyLine()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found - DerefACI(); - get_line(); - if (! strcmp(m_str,"VERTEX")) - { - double vertex[3] = {0,0,0}; - if (CDxfRead::ReadVertex(vertex, &bulge_found, &bulge)) - { - if(!first_vertex_section_found) { - first_vertex_section_found = true; - memcpy(first_vertex, vertex, 3*sizeof(double)); - } - AddPolyLinePoint(this, vertex[0], vertex[1], vertex[2], bulge_found, bulge); - break; - } - } - if (! strcmp(m_str,"SEQEND")) - { - if(closed && first_vertex_section_found) { - AddPolyLinePoint(this, first_vertex[0], first_vertex[1], first_vertex[2], 0, 0); - } - first_vertex_section_found = false; - PolyLineStart(); - return(true); - } - break; - case 70: - // flags - get_line(); - if(sscanf(m_str, "%d", &flags) != 1)return false; - closed = ((flags & 1) != 0); - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - default: - // skip the next line - get_line(); - break; + + get_line(); + switch (n) { + case 0: + // next item found + ResolveColorIndex(); + if (m_str == "VERTEX") { + Dxf_VERTEX vertex; + if (ReadVertex(&vertex)) + polyline.vertices.push_back(std::move(vertex)); + } + + if (m_str == "SEQEND") { + OnReadPolyline(polyline); + return true; + } + + break; + case 70: + // flags + polyline.flags = stringToUnsigned(m_str); + break; + default: + HandleCommonGroupCode(n); + break; } } return false; } -void CDxfRead::OnReadArc(double start_angle, double end_angle, double radius, const double* c, double z_extrusion_dir, bool hidden){ - double s[3] = {0,0,0}, e[3] = {0,0,0}, temp[3] = {0,0,0}; - if (z_extrusion_dir==1.0) - { - temp[0] =c[0]; - temp[1] =c[1]; - temp[2] =c[2]; - s[0] = c[0] + radius * cos(start_angle * M_PI/180); - s[1] = c[1] + radius * sin(start_angle * M_PI/180); - s[2] = c[2]; - e[0] = c[0] + radius * cos(end_angle * M_PI/180); - e[1] = c[1] + radius * sin(end_angle * M_PI/180); - e[2] = c[2]; - } - else - { - temp[0] =-c[0]; - temp[1] =c[1]; - temp[2] =c[2]; - - e[0] = -(c[0] + radius * cos(start_angle * M_PI/180)); - e[1] = (c[1] + radius * sin(start_angle * M_PI/180)); - e[2] = c[2]; - s[0] = -(c[0] + radius * cos(end_angle * M_PI/180)); - s[1] = (c[1] + radius * sin(end_angle * M_PI/180)); - s[2] = c[2]; - +void CDxfRead::OnReadArc(double start_angle, + double end_angle, + double radius, + const DxfCoords& c, + double z_extrusion_dir, + bool hidden) +{ + DxfCoords s = {}; + DxfCoords e = {}; + DxfCoords temp = {}; + if (z_extrusion_dir == 1.0) { + temp.x = c.x; + temp.y = c.y; + temp.z = c.z; + s.x = c.x + radius * cos(start_angle * M_PI / 180); + s.y = c.y + radius * sin(start_angle * M_PI / 180); + s.z = c.z; + e.x = c.x + radius * cos(end_angle * M_PI / 180); + e.y = c.y + radius * sin(end_angle * M_PI / 180); + e.z = c.z; + } + else { + temp.x = -c.x; + temp.y = c.y; + temp.z = c.z; + + e.x = -(c.x + radius * cos(start_angle * M_PI/180)); + e.y = (c.y + radius * sin(start_angle * M_PI/180)); + e.z = c.z; + s.x = -(c.x + radius * cos(end_angle * M_PI/180)); + s.y = (c.y + radius * sin(end_angle * M_PI/180)); + s.z = c.z; } + OnReadArc(s, e, temp, true, hidden); } -void CDxfRead::OnReadCircle(const double* c, double radius, bool hidden){ - double s[3]; - double start_angle = 0; - s[0] = c[0] + radius * cos(start_angle * M_PI/180); - s[1] = c[1] + radius * sin(start_angle * M_PI/180); - s[2] = c[2]; +void CDxfRead::OnReadCircle(const DxfCoords& c, double radius, bool hidden) +{ + constexpr double start_angle = 0; + const DxfCoords s = { + c.x + radius * cos(start_angle * M_PI / 180), + c.y + radius * sin(start_angle * M_PI / 180), + c.z + }; - OnReadCircle(s, c, false, hidden); //false to change direction because otherwise the arc length is zero + const bool dir = false; // 'false' to change direction because otherwise the arc length is zero + OnReadCircle(s, c, dir, hidden); } -void CDxfRead::OnReadEllipse(const double* c, const double* m, double ratio, double start_angle, double end_angle){ - double major_radius = sqrt(m[0]*m[0] + m[1]*m[1] + m[2]*m[2]); - double minor_radius = major_radius * ratio; - - //Since we only support 2d stuff, we can calculate the rotation from the major axis x and y value only, - //since z is zero, major_radius is the vector length - - double rotation = atan2(m[1]/major_radius,m[0]/major_radius); +void CDxfRead::OnReadEllipse(const DxfCoords& c, + const DxfCoords& m, + double ratio, + double start_angle, + double end_angle) +{ + const double major_radius = sqrt(m.x * m.x + m.y * m.y + m.z * m.z); + const double minor_radius = major_radius * ratio; + // Since we only support 2d stuff, we can calculate the rotation from the major axis x and y + // value only, since z is zero, major_radius is the vector length + const double rotation = atan2(m.y / major_radius, m.x / major_radius); OnReadEllipse(c, major_radius, minor_radius, rotation, start_angle, end_angle, true); } - bool CDxfRead::ReadInsert() { - double c[3] = {0,0,0}; // coordinate - double s[3] = {1,1,1}; // scale - double rot = 0.0; // rotation - char name[1024] = {0}; + Dxf_INSERT insert; - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found + ResolveColorIndex(); + OnReadInsert(insert); + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadInsert()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found - DerefACI(); - OnReadInsert(c, s, name, rot * M_PI/180); - return(true); - case 8: - // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - case 10: - // coord x - get_line(); - ss.str(m_str); ss >> c[0]; c[0] = mm(c[0]); if(ss.fail()) return false; - break; - case 20: - // coord y - get_line(); - ss.str(m_str); ss >> c[1]; c[1] = mm(c[1]); if(ss.fail()) return false; - break; - case 30: - // coord z - get_line(); - ss.str(m_str); ss >> c[2]; c[2] = mm(c[2]); if(ss.fail()) return false; - break; - case 41: - // scale x - get_line(); - ss.str(m_str); ss >> s[0]; if(ss.fail()) return false; - break; - case 42: - // scale y - get_line(); - ss.str(m_str); ss >> s[1]; if(ss.fail()) return false; - break; - case 43: - // scale z - get_line(); - ss.str(m_str); ss >> s[2]; if(ss.fail()) return false; - break; - case 50: - // rotation - get_line(); - ss.str(m_str); ss >> rot; if(ss.fail()) return false; - break; - case 2: - // block name - get_line(); - safe_strcpy(name, m_str); - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + + get_line(); + switch (n){ + case 2: + insert.blockName = m_str; + break; + case 10: case 20: case 30: + HandleCoordCode(n, &insert.insertPoint); + break; + case 41: + insert.scaleFactor.x = stringToDouble(m_str); + break; + case 42: + insert.scaleFactor.y = stringToDouble(m_str); + break; + case 43: + insert.scaleFactor.z = stringToDouble(m_str); + break; + case 50: + insert.rotationAngle = stringToDouble(m_str); + break; + case 70: + insert.columnCount = stringToInt(m_str); + break; + case 71: + insert.rowCount = stringToInt(m_str); + break; + case 44: + insert.columnSpacing = mm(stringToDouble(m_str)); + break; + case 45: + insert.rowSpacing = mm(stringToDouble(m_str)); + break; + case 210: case 220: case 230: + HandleCoordCode<210, 220, 230>(n, &insert.extrusionDirection); + break; + default: + HandleCommonGroupCode(n); + break; } } + return false; } - bool CDxfRead::ReadDimension() { - double s[3] = {0,0,0}; // startpoint - double e[3] = {0,0,0}; // endpoint - double p[3] = {0,0,0}; // dimpoint + DxfCoords s = {}; // startpoint + DxfCoords e = {}; // endpoint + DxfCoords p = {}; // dimpoint double rot = -1.0; // rotation - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + // next item found + ResolveColorIndex(); + OnReadDimension(s, e, p, rot * M_PI/180); + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadDimension()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: - // next item found - DerefACI(); - OnReadDimension(s, e, p, rot * M_PI/180); - return(true); - case 8: - // Layer name follows - get_line(); - safe_strcpy(m_layer_name, m_str); - break; - case 13: - // start x - get_line(); - ss.str(m_str); ss >> s[0]; s[0] = mm(s[0]); if(ss.fail()) return false; - break; - case 23: - // start y - get_line(); - ss.str(m_str); ss >> s[1]; s[1] = mm(s[1]); if(ss.fail()) return false; - break; - case 33: - // start z - get_line(); - ss.str(m_str); ss >> s[2]; s[2] = mm(s[2]); if(ss.fail()) return false; - break; - case 14: - // end x - get_line(); - ss.str(m_str); ss >> e[0]; e[0] = mm(e[0]); if(ss.fail()) return false; - break; - case 24: - // end y - get_line(); - ss.str(m_str); ss >> e[1]; e[1] = mm(e[1]); if(ss.fail()) return false; - break; - case 34: - // end z - get_line(); - ss.str(m_str); ss >> e[2]; e[2] = mm(e[2]); if(ss.fail()) return false; - break; - case 10: - // dimline x - get_line(); - ss.str(m_str); ss >> p[0]; p[0] = mm(p[0]); if(ss.fail()) return false; - break; - case 20: - // dimline y - get_line(); - ss.str(m_str); ss >> p[1]; p[1] = mm(p[1]); if(ss.fail()) return false; - break; - case 30: - // dimline z - get_line(); - ss.str(m_str); ss >> p[2]; p[2] = mm(p[2]); if(ss.fail()) return false; - break; - case 50: - // rotation - get_line(); - ss.str(m_str); ss >> rot; if(ss.fail()) return false; - break; - case 62: - // color index - get_line(); - ss.str(m_str); ss >> m_aci; if(ss.fail()) return false; - break; - case 100: - case 39: - case 210: - case 220: - case 230: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + + get_line(); + switch (n){ + case 13: case 23: case 33: + // start coords + HandleCoordCode<13, 23, 33>(n, &s); + break; + case 14: case 24: case 34: + // end coords + HandleCoordCode<14, 24, 34>(n, &e); + break; + case 10: case 20: case 30: + // dimline coords + HandleCoordCode<10, 20, 30>(n, &p); + break; + case 50: + // rotation + rot = stringToDouble(m_str); + break; + case 100: + case 39: + case 210: + case 220: + case 230: + // skip the next line + break; + default: + HandleCommonGroupCode(n); + break; } } + return false; } bool CDxfRead::ReadBlockInfo() { - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; - if(sscanf(m_str, "%d", &n) != 1) - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadBlockInfo()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 2: - // block name - get_line(); - safe_strcpy(m_block_name, m_str); - return true; - case 3: - // block name too??? - get_line(); - safe_strcpy(m_block_name, m_str); - return true; - default: - // skip the next line - get_line(); - break; + + get_line(); + switch (n){ + case 2: + // block name + m_block_name = m_str; + return true; + case 3: + // block name too??? + m_block_name = m_str; + return true; + default: + // skip the next line + break; } } + return false; } - void CDxfRead::get_line() { - if (m_unused_line[0] != '\0') - { - safe_strcpy(m_str, m_unused_line); - memset( m_unused_line, '\0', sizeof(m_unused_line)); + if (!m_unused_line.empty()) { + m_str = m_unused_line; + m_unused_line.clear(); return; } - m_ifs.getline(m_str, 1024); - ++m_lineNum; + std::getline(m_ifs, m_str); + m_gcount = m_str.size(); + ++m_line_nb; - char str[1024]; - size_t len = strlen(m_str); - int j = 0; - bool non_white_found = false; - for(size_t i = 0; i= size) ? size - 1 : ret; - memcpy(dst, src, len); - dst[len] = '\0'; + ++itNonSpace; } + + m_str.erase(m_str.begin(), itNonSpace); } -void CDxfRead::put_line(const char *value) +void CDxfRead::put_line(const std::string& value) { - dxf_strncpy( m_unused_line, value, sizeof(m_unused_line) ); + m_unused_line = value; } - -bool CDxfRead::ReadUnits() +bool CDxfRead::ReadInsUnits() { get_line(); // Skip to next line. get_line(); // Skip to next line. - int n = 0; - if(sscanf(m_str, "%d", &n) == 1) - { - m_eUnits = eDxfUnits_t( n ); - return(true); - } // End if - then - else - { + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (!isStringToErrorValue(n)) { + m_eUnits = static_cast(n); + return true; + } + else { this->ReportError_readInteger("DXF::ReadUnits()"); - return(false); + return false; } } +bool CDxfRead::ReadMeasurement() +{ + get_line(); + get_line(); + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) + m_measurement_inch = true; + + return true; +} bool CDxfRead::ReadLayer() { std::string layername; - int aci = -1; + ColorIndex_t colorIndex = -1; - while(!m_ifs.eof()) - { + while (!m_ifs.eof()) { get_line(); - int n; + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + if (layername.empty()) { + this->ReportError_readInteger("DXF::ReadLayer() - no layer name"); + return false; + } - if(sscanf(m_str, "%d", &n) != 1) - { + m_layer_ColorIndex_map[layername] = colorIndex; + return true; + } + else if (isStringToErrorValue(n)) { this->ReportError_readInteger("DXF::ReadLayer()"); return false; } - std::istringstream ss; - ss.imbue(std::locale("C")); - switch(n){ - case 0: // next item found, so finish with line - if (layername.empty()) - return false; + get_line(); + switch (n) { + case 2: // Layer name follows + layername = m_str; + break; + case 62: + // layer color ; if negative, layer is off + colorIndex = stringToInt(m_str); + break; + case 6: // linetype name + case 70: // layer flags + case 100: + case 290: + case 370: + case 390: + // skip the next line + break; + default: + // skip the next line + break; + } + } - m_layer_aci[layername] = aci; - return true; + return false; +} - case 2: // Layer name follows - get_line(); - layername = m_str; - break; +bool CDxfRead::ReadStyle() +{ + Dxf_STYLE style; - case 62: - // layer color ; if negative, layer is off - get_line(); - if(sscanf(m_str, "%d", &aci) != 1)return false; - break; + while (!m_ifs.eof()) { + get_line(); + const int n = stringToInt(m_str, StringToErrorMode::ReturnErrorValue); + if (n == 0) { + if (style.name.empty()) { + this->ReportError_readInteger("DXF::ReadStyle() - no style name"); + return false; + } - case 6: // linetype name - case 70: // layer flags - case 100: - case 290: - case 370: - case 390: - // skip the next line - get_line(); - break; - default: - // skip the next line - get_line(); - break; + m_mapStyle.insert({ style.name, style }); + return true; + } + else if (isStringToErrorValue(n)) { + this->ReportError_readInteger("DXF::ReadStyle()"); + return false; + } + + get_line(); + switch (n) { + case 2: + style.name = m_str; + break; + case 40: + style.fixedTextHeight = mm(stringToDouble(m_str)); + break; + case 41: + style.widthFactor = stringToDouble(m_str); + break; + case 50: + style.obliqueAngle = stringToDouble(m_str); + break; + case 3: + style.primaryFontFileName = m_str; + break; + case 4: + style.bigFontFileName = m_str; + break; + default: + break; // skip the next line } } + return false; } -void CDxfRead::DoRead(const bool ignore_errors /* = false */ ) +bool CDxfRead::ReadAcadVer() { - m_ignore_errors = ignore_errors; - if(m_fail)return; + static const std::vector VersionNames = { + // This table is indexed by eDXFVersion_t - (ROlder+1) + "AC1006", + "AC1009", + "AC1012", + "AC1014", + "AC1015", + "AC1018", + "AC1021", + "AC1024", + "AC1027", + "AC1032" + }; + assert(VersionNames.size() == RNewer - ROlder - 1); get_line(); + get_line(); + auto first = VersionNames.cbegin(); + auto last = VersionNames.cend(); + auto found = std::lower_bound(first, last, m_str); + if (found == last) { + m_version = RNewer; + } + else if (*found == m_str) { + m_version = (eDXFVersion_t)(std::distance(first, found) + (ROlder + 1)); + } + else if (found == first) { + m_version = ROlder; + } + else { + m_version = RUnknown; + } - while(!m_ifs.eof()) - { - m_aci = 256; + return ResolveEncoding(); +} - if (!strcmp( m_str, "$INSUNITS" )){ - if (!ReadUnits())return; - continue; - } // End if - then +bool CDxfRead::ReadDwgCodePage() +{ + get_line(); + get_line(); + m_CodePage = m_str; - if (!strcmp( m_str, "$MEASUREMENT" )){ - get_line(); - get_line(); - int n = 1; - if(sscanf(m_str, "%d", &n) == 1) - { - if(n == 0)m_measurement_inch = true; - } - continue; - } // End if - then - - else if(!strcmp(m_str, "0")) - { - get_line(); - if (!strcmp( m_str, "SECTION" )){ - safe_strcpy(m_section_name, ""); - get_line(); - get_line(); - if (strcmp( m_str, "ENTITIES" )) - safe_strcpy(m_section_name, m_str); - safe_strcpy(m_block_name, ""); - - } // End if - then - else if (!strcmp( m_str, "TABLE" )){ - get_line(); - get_line(); - } - - else if (!strcmp( m_str, "LAYER" )){ - if(!ReadLayer()) - { - this->ReportError("DXF::DoRead() - Failed to read layer"); - //return; Some objects or tables can have "LAYER" as name... - } - continue; - } - - else if (!strcmp( m_str, "BLOCK" )) { - if(!ReadBlockInfo()) - { - this->ReportError("DXF::DoRead() - Failed to read block info"); - return; - } - continue; - } // End if - then - - else if (!strcmp( m_str, "ENDSEC" )){ - safe_strcpy(m_section_name, ""); - safe_strcpy(m_block_name, ""); - } // End if - then - else if(!strcmp(m_str, "LINE")){ - if(!ReadLine()) - { - this->ReportError("DXF::DoRead() - Failed to read line"); - return; - } - continue; - } - else if(!strcmp(m_str, "ARC")){ - if(!ReadArc()) - { - this->ReportError("DXF::DoRead() - Failed to read arc"); - return; - } - continue; - } - else if(!strcmp(m_str, "CIRCLE")){ - if(!ReadCircle()) - { - this->ReportError("DXF::DoRead() - Failed to read circle"); - return; - } - continue; - } - else if(!strcmp(m_str, "MTEXT")){ - if(!ReadText()) - { - this->ReportError("DXF::DoRead() - Failed to read text"); - return; - } - continue; - } - else if(!strcmp(m_str, "TEXT")){ - if(!ReadText()) - { - this->ReportError("DXF::DoRead() - Failed to read text"); - return; - } - continue; - } - else if(!strcmp(m_str, "ELLIPSE")){ - if(!ReadEllipse()) - { - this->ReportError("DXF::DoRead() - Failed to read ellipse"); - return; - } - continue; - } - else if(!strcmp(m_str, "SPLINE")){ - if(!ReadSpline()) - { - this->ReportError("DXF::DoRead() - Failed to read spline"); - return; - } - continue; - } - else if (!strcmp(m_str, "LWPOLYLINE")) { - if(!ReadLwPolyLine()) - { - this->ReportError("DXF::DoRead() - Failed to read LW Polyline"); - return; - } - continue; - } - else if (!strcmp(m_str, "POLYLINE")) { - if(!ReadPolyLine()) - { - this->ReportError("DXF::DoRead() - Failed to read Polyline"); + return ResolveEncoding(); +} + +bool CDxfRead::ResolveEncoding() +{ + // + // See https://ezdxf.readthedocs.io/en/stable/dxfinternals/fileencoding.html# + // + + if (m_version >= R2007) { // Note this does not include RUnknown, but does include RLater + return this->setSourceEncoding("UTF8"); + } + else { + std::transform(m_CodePage.cbegin(), m_CodePage.cend(), m_CodePage.begin(), [](char c) { + return std::toupper(c, std::locale::classic()); + }); + // ANSI_1252 by default if $DWGCODEPAGE is not set + if (m_CodePage.empty()) + m_CodePage = "ANSI_1252"; + + return this->setSourceEncoding(m_CodePage); + } +} + +void CDxfRead::HandleCommonGroupCode(int n) +{ + switch (n) { + case 8: + // layer name + m_layer_name = m_str; + break; + case 62: + // color index + m_ColorIndex = stringToInt(m_str); + break; + } +} + +void CDxfRead::DoRead(bool ignore_errors) +{ + m_ignore_errors = ignore_errors; + m_gcount = 0; + if (m_fail) + return; + + std::unordered_map> mapHeaderVarHandler; + mapHeaderVarHandler.insert({ "$INSUNITS", [=]{ return ReadInsUnits(); } }); + mapHeaderVarHandler.insert({ "$MEASUREMENT", [=]{ return ReadMeasurement(); } }); + mapHeaderVarHandler.insert({ "$ACADVER", [=]{ return ReadAcadVer(); } }); + mapHeaderVarHandler.insert({ "$DWGCODEPAGE", [=]{ return ReadDwgCodePage(); } }); + + std::unordered_map> mapEntityHandler; + mapEntityHandler.insert({ "ARC", [=]{ return ReadArc(); } }); + mapEntityHandler.insert({ "BLOCK", [=]{ return ReadBlockInfo(); } }); + mapEntityHandler.insert({ "CIRCLE", [=]{ return ReadCircle(); } }); + mapEntityHandler.insert({ "DIMENSION", [=]{ return ReadDimension(); } }); + mapEntityHandler.insert({ "ELLIPSE", [=]{ return ReadEllipse(); } }); + mapEntityHandler.insert({ "INSERT", [=]{ return ReadInsert(); } }); + mapEntityHandler.insert({ "LAYER", [=]{ return ReadLayer(); } }); + mapEntityHandler.insert({ "LINE", [=]{ return ReadLine(); } }); + mapEntityHandler.insert({ "LWPOLYLINE", [=]{ return ReadLwPolyLine(); } }); + mapEntityHandler.insert({ "MTEXT", [=]{ return ReadMText(); } }); + mapEntityHandler.insert({ "POINT", [=]{ return ReadPoint(); } }); + mapEntityHandler.insert({ "POLYLINE", [=]{ return ReadPolyLine(); } }); + mapEntityHandler.insert({ "SECTION", [=]{ return ReadSection(); } }); + mapEntityHandler.insert({ "SOLID", [=]{ return ReadSolid(); } }); + mapEntityHandler.insert({ "SPLINE", [=]{ return ReadSpline(); } }); + mapEntityHandler.insert({ "STYLE", [=]{ return ReadStyle(); } }); + mapEntityHandler.insert({ "TEXT", [=]{ return ReadText(); } }); + mapEntityHandler.insert({ "TABLE", [=]{ return ReadTable(); } }); + mapEntityHandler.insert({ "ENDSEC", [=]{ return ReadEndSec(); } }); + + get_line(); + + ScopedCLocale _(LC_NUMERIC); + while (!m_ifs.eof()) { + m_ColorIndex = ColorBylayer; // Default + + { // Handle header variable + auto itHandler = mapHeaderVarHandler.find(m_str); + if (itHandler != mapHeaderVarHandler.cend()) { + const auto& fn = itHandler->second; + if (fn()) + continue; + else return; - } - continue; } - else if (!strcmp(m_str, "POINT")) { - if(!ReadPoint()) - { - this->ReportError("DXF::DoRead() - Failed to read Point"); - return; + } + + if (m_str == "0") { + get_line(); + if (m_str == "0") + get_line(); // Skip again + + auto itHandler = mapEntityHandler.find(m_str); + if (itHandler != mapEntityHandler.cend()) { + const auto& fn = itHandler->second; + bool okRead = false; + std::string exceptionMsg; + try { + okRead = fn(); + } catch (const std::runtime_error& err) { + exceptionMsg = err.what(); } - continue; - } - else if (!strcmp(m_str, "INSERT")) { - if(!ReadInsert()) - { - this->ReportError("DXF::DoRead() - Failed to read Insert"); - return; + + if (okRead) { + continue; } - continue; - } - else if (!strcmp(m_str, "DIMENSION")) { - if(!ReadDimension()) - { - this->ReportError("DXF::DoRead() - Failed to read Dimension"); - return; + else { + std::string errMsg = "DXF::DoRead() - Failed to read " + m_str; + if (!exceptionMsg.empty()) + errMsg += "\nError: " + exceptionMsg; + + this->ReportError(errMsg); + if (m_str == "LAYER") // Some objects or tables can have "LAYER" as name... + continue; + else + return; } - continue; } } get_line(); } + AddGraphics(); } -void CDxfRead::DerefACI() +void CDxfRead::ResolveColorIndex() { - - if (m_aci == 256) // if color = layer color, replace by color from layer - { - m_aci = m_layer_aci[std::string(m_layer_name)]; - } + if (m_ColorIndex == ColorBylayer) // if color = layer color, replace by color from layer + m_ColorIndex = m_layer_ColorIndex_map[m_layer_name]; } void CDxfRead::ReportError_readInteger(const char* context) @@ -3422,29 +3529,28 @@ void CDxfRead::ReportError_readInteger(const char* context) std::streamsize CDxfRead::gcount() const { - return m_ifs.gcount(); + // std::getline() doesn't affect std::istream::gcount + //return m_ifs.gcount(); + return m_gcount; } std::string CDxfRead::LayerName() const { std::string result; - if (strlen(m_section_name) > 0) - { + if (!m_section_name.empty()) { result.append(m_section_name); result.append(" "); } - if (strlen(m_block_name) > 0) - { + if (!m_block_name.empty()) { result.append(m_block_name); result.append(" "); } - if (strlen(m_layer_name) > 0) - { + if (!m_layer_name.empty()) { result.append(m_layer_name); } - return(result); + return result; } diff --git a/src/io_dxf/dxf.h b/src/io_dxf/dxf.h index 9cd8b05e..369bcc3f 100644 --- a/src/io_dxf/dxf.h +++ b/src/io_dxf/dxf.h @@ -3,30 +3,28 @@ // This program is released under the BSD license. See the file COPYING for details. // modified 2018 wandererfan -// MAYO: file initially taken from FreeCad/src/Mod/Import/App/dxf.h -- commit #47d5707 +// MAYO: file taken from FreeCad/src/Mod/Import/App/dxf.h -- commit #55292e9 #pragma once #include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include #include -#include #include -#include -#include -#include -#include +#include +#include +#include #include "freecad.h" -//Following is required to be defined on Ubuntu with OCC 6.3.1 -#ifndef HAVE_IOSTREAM -#define HAVE_IOSTREAM -#endif - -typedef int Aci_t; // AutoCAD color index +typedef int ColorIndex_t; // DXF color index typedef enum { @@ -53,11 +51,233 @@ typedef enum eParsecs } eDxfUnits_t; +struct DxfCoords { + double x; + double y; + double z; +}; + +struct DxfScale { + double x; + double y; + double z; +}; + +struct Dxf_STYLE { + // Code: 2 + std::string name; + // Code: 40 + double fixedTextHeight = 0; + // Code: 41 + double widthFactor = 1.; + // Code: 50 + // AutoCad documentation doesn't specify units, but "Group Codes in Numerical Order" section + // states that codes 50-58 are in degrees + double obliqueAngle = 0.; + // Code: 3 + std::string primaryFontFileName; + // Code: 4 + std::string bigFontFileName; + + // TODO Code 70(standard flag values) + // TODO Code 71(text generation flags) + // TODO Code 42(last height used) +}; + +struct Dxf_TEXT { + // Code: 39 + double thickness = 0.; + // Code: 10, 20, 30 + DxfCoords firstAlignmentPoint = {}; + // Code: 40 + double height = 0.; + // Code: 1 + std::string str; + // Code: 50 + // AutoCad documentation doesn't specify units, but "Group Codes in Numerical Order" section + // states that codes 50-58 are in degrees + double rotationAngle = 0.; + // Code: 41 + // "This value is also adjusted when fit-type text is used" + double relativeXScaleFactorWidth = 1.; + // Code: 51 + // AutoCad documentation doesn't specify units, but "Group Codes in Numerical Order" section + // states that codes 50-58 are in degrees + double obliqueAngle = 0.; + // Code: 7 + std::string styleName; + + // TODO Code 71(text generation flags) + + enum class HorizontalJustification { + Left = 0, Center = 1, Right = 2, Aligned = 3, Middle = 4, Fit = 5 + }; + // Code: 72 + HorizontalJustification horizontalJustification = HorizontalJustification::Left; + // Code: 11, 21, 31 + DxfCoords secondAlignmentPoint = {}; + // Code: 210, 220, 230 + DxfCoords extrusionDirection = {0., 0., 1.}; + + enum class VerticalJustification { + Baseline = 0, Bottom = 1, Middle = 2, Top = 3 + }; + // Code: 73 + VerticalJustification verticalJustification = VerticalJustification::Baseline; +}; + +struct Dxf_MTEXT { + enum class AttachmentPoint { + TopLeft = 1, TopCenter, TopRight, + MiddleLeft, MiddleCenter, MiddleRight, + BottomLeft, BottomCenter, BottomRight + }; + + // Code: 10, 20, 30 + DxfCoords insertionPoint = {}; + // Code: 40 + double height = 0.; + // Code: 41 + double referenceRectangleWidth = 0; + // Code 71 + AttachmentPoint attachmentPoint = AttachmentPoint::TopLeft; + + // TODO Code 72(drawing direction) + + // Code: 1, 3 + std::string str; + + // TODO Code 7(text sytle name) + + // Code: 210, 220, 230 + DxfCoords extrusionDirection = {0., 0., 1.}; + + // Code: 11, 21, 31 + DxfCoords xAxisDirection = {1., 0., 0.}; // WCS + + // NOTE AutoCad documentation states that codes 42, 43 are "read-only, ignored if supplied" + + double rotationAngle = 0.; // radians(AutoCad documentation) + + // TODO Code 73(line spacing style) + // TODO Code 44(line spacing factor) + // TODO Code 90(background fill setting) + // TODO Code 420-429(background color, if RGB) + // TODO Code 430-439(background color, if name) + // TODO Code 45(fill box scale) + // TODO Code 63(background fill color) + // TODO Code 441(transparency of background fill color) + // TODO Codes for columns: 75, 76, 78, 79, 48, 49, 50 + + enum class ColumnType { None = 0, Static, Dynamic }; + bool acadHasColumnInfo = false; + ColumnType acadColumnInfo_Type = ColumnType::None; + bool acadColumnInfo_AutoHeight = false; + int acadColumnInfo_Count = 0; + bool acadColumnInfo_FlowReversed = false; + double acadColumnInfo_Width = 0.; + double acadColumnInfo_GutterWidth = 0.; + + bool acadHasDefinedHeight = false; + double acadDefinedHeight = 0.; + +}; + +struct Dxf_VERTEX { + enum class Bulge { StraightSegment = 0, SemiCircle = 1 }; + enum Flag { + None = 0, + ExtraVertex = 1, + CurveFitTangent = 2, + NotUsed = 4, + SplineVertex = 8, + SplineFrameControlPoint = 16, + Polyline3dVertex = 32, + Polygon3dVertex = 64, + PolyfaceMesgVertex = 128 + }; + using Flags = unsigned; + + DxfCoords point = {}; + Bulge bulge = Bulge::StraightSegment; + Flags flags = Flag::None; +}; + +struct Dxf_POLYLINE { + enum Flag { + None = 0, + Closed = 1, + CurveFit = 2, + SplineFit = 4, + Polyline3d = 8, + PolygonMesh3d = 16, + PolygonMeshClosedNDir = 32, + PolyfaceMesh = 64, + ContinuousLinetypePattern = 128 + }; + using Flags = unsigned; + + enum Type { + NoSmoothSurfaceFitted = 0, + QuadraticBSplineSurface = 5, + CubicBSplineSurface = 6, + BezierSurface = 8 + }; + + double elevation = 0.; + double thickness = 0.; + Flags flags = Flag::None; + double defaultStartWidth = 0.; + double defaultEndWidth = 0.; + int polygonMeshMVertexCount = 0; + int polygonMeshNVertexCount = 0; + double smoothSurfaceMDensity = 0.; + double smoothSurfaceNDensity = 0.; + double extrusionDir[3] = { 0., 0., 1. }; + std::vector vertices; +}; + +struct Dxf_INSERT { + // Code: 2 + std::string blockName; + // Code: 10, 20, 30 + DxfCoords insertPoint = {}; // OCS + // Code: 41, 42, 43 + DxfScale scaleFactor = { 1., 1., 1. }; + // Code: 50 + double rotationAngle = 0.; + // Code: 70 + int columnCount = 1; + // Code: 71 + int rowCount = 1; + // Code: 44 + double columnSpacing = 0.; + // Code: 45 + double rowSpacing = 0.; + // Code: 210, 220, 230 + DxfCoords extrusionDirection = { 0., 0., 1. }; +}; + +struct Dxf_SOLID { + // Code: 10, 20, 30 + DxfCoords corner1; + // Code: 11, 21, 31 + DxfCoords corner2; + // Code: 12, 22, 32 + DxfCoords corner3; + // Code: 13, 23, 33 + DxfCoords corner4; + bool hasCorner4 = false; + // Code: 39 + double thickness = 0.; + // Code: 210, 220, 230 + DxfCoords extrusionDirection = { 0., 0., 1. }; +}; //spline data for reading struct SplineData { - double norm[3]; + DxfCoords norm; int degree; int knots; int control_points; @@ -118,9 +338,26 @@ struct LWPolyDataOut std::vector Bulge; point3D Extr; }; +typedef enum +{ + RUnknown, + ROlder, + R10, + R11_12, + R13, + R14, + R2000, + R2004, + R2007, + R2010, + R2013, + R2018, + RNewer, +} eDXFVersion_t; //******************** -class CDxfWrite{ +class CDxfWrite +{ private: std::ofstream m_ofs; bool m_fail; @@ -130,29 +367,45 @@ class CDxfWrite{ std::ostringstream m_ssLayer; protected: - void putLine(const Base::Vector3d& s, const Base::Vector3d& e, - std::ostringstream& outStream, const std::string& handle, + void putLine(const Base::Vector3d& s, + const Base::Vector3d& e, + std::ostringstream& outStream, + const std::string& handle, const std::string& ownerHandle); - void putText(const char* text, const Base::Vector3d& location1, const Base::Vector3d& location2, - const double height, const int horizJust, - std::ostringstream& outStream, const std::string& handle, + void putText(const char* text, + const Base::Vector3d& location1, + const Base::Vector3d& location2, + const double height, + const int horizJust, + std::ostringstream& outStream, + const std::string& handle, const std::string& ownerHandle); - void putArrow(const Base::Vector3d& arrowPos, const Base::Vector3d& barb1Pos, const Base::Vector3d& barb2Pos, - std::ostringstream& outStream, const std::string& handle, + void putArrow(const Base::Vector3d& arrowPos, + const Base::Vector3d& barb1Pos, + const Base::Vector3d& barb2Pos, + std::ostringstream& outStream, + const std::string& handle, const std::string& ownerHandle); //! copy boiler plate file std::string getPlateFile(const std::string& fileSpec); - void setDataDir(const std::string& s) { m_dataDir = s; } - std::string getHandle(void); - std::string getEntityHandle(void); - std::string getLayerHandle(void); - std::string getBlockHandle(void); - std::string getBlkRecordHandle(void); + void setDataDir(const std::string& s) + { + m_dataDir = s; + } + std::string getHandle(); + std::string getEntityHandle(); + std::string getLayerHandle(); + std::string getBlockHandle(); + std::string getBlkRecordHandle(); std::string m_optionSource; int m_version; int m_handle; + int m_entityHandle; + int m_layerHandle; + int m_blockHandle; + int m_blkRecordHandle; bool m_polyOverride; std::string m_saveModelSpaceHandle; @@ -170,94 +423,165 @@ class CDxfWrite{ CDxfWrite(const char* filepath); ~CDxfWrite(); - void init(void); - void endRun(void); + void init(); + void endRun(); - bool Failed(){return m_fail;} + bool Failed() + { + return m_fail; + } // void setOptions(void); // bool isVersionValid(int vers); - std::string getLayerName() { return m_layerName; } + std::string getLayerName() + { + return m_layerName; + } void setLayerName(std::string s); - void setVersion(int v) { m_version = v;} - void setPolyOverride(bool b) { m_polyOverride = b; } + void setVersion(int v) + { + m_version = v; + } + void setPolyOverride(bool b) + { + m_polyOverride = b; + } void addBlockName(std::string s, std::string blkRecordHandle); void writeLine(const double* s, const double* e); void writePoint(const double*); void writeArc(const double* s, const double* e, const double* c, bool dir); - void writeEllipse(const double* c, double major_radius, double minor_radius, - double rotation, double start_angle, double end_angle, bool endIsCW); + void writeEllipse(const double* c, + double major_radius, + double minor_radius, + double rotation, + double start_angle, + double end_angle, + bool endIsCW); void writeCircle(const double* c, double radius ); - void writeSpline(const SplineDataOut &sd); - void writeLWPolyLine(const LWPolyDataOut &pd); - void writePolyline(const LWPolyDataOut &pd); + void writeSpline(const SplineDataOut& sd); + void writeLWPolyLine(const LWPolyDataOut& pd); + void writePolyline(const LWPolyDataOut& pd); void writeVertex(double x, double y, double z); - void writeText(const char* text, const double* location1, const double* location2, - const double height, const int horizJust); - void writeLinearDim(const double* textMidPoint, const double* lineDefPoint, - const double* extLine1, const double* extLine2, - const char* dimText, int type); - void writeLinearDimBlock(const double* textMidPoint, const double* lineDefPoint, - const double* extLine1, const double* extLine2, - const char* dimText, int type); - void writeAngularDim(const double* textMidPoint, const double* lineDefPoint, - const double* startExt1, const double* endExt1, - const double* startExt2, const double* endExt2, - const char* dimText); - void writeAngularDimBlock(const double* textMidPoint, const double* lineDefPoint, - const double* startExt1, const double* endExt1, - const double* startExt2, const double* endExt2, - const char* dimText); - void writeRadialDim(const double* centerPoint, const double* textMidPoint, - const double* arcPoint, - const char* dimText); - void writeRadialDimBlock(const double* centerPoint, const double* textMidPoint, - const double* arcPoint, const char* dimText); - void writeDiametricDim(const double* textMidPoint, - const double* arcPoint1, const double* arcPoint2, - const char* dimText); - void writeDiametricDimBlock(const double* textMidPoint, - const double* arcPoint1, const double* arcPoint2, + void writeText(const char* text, + const double* location1, + const double* location2, + const double height, + const int horizJust); + void writeLinearDim(const double* textMidPoint, + const double* lineDefPoint, + const double* extLine1, + const double* extLine2, + const char* dimText, + int type); + void writeLinearDimBlock(const double* textMidPoint, + const double* lineDefPoint, + const double* extLine1, + const double* extLine2, + const char* dimText, + int type); + void writeAngularDim(const double* textMidPoint, + const double* lineDefPoint, + const double* startExt1, + const double* endExt1, + const double* startExt2, + const double* endExt2, const char* dimText); + void writeAngularDimBlock(const double* textMidPoint, + const double* lineDefPoint, + const double* startExt1, + const double* endExt1, + const double* startExt2, + const double* endExt2, + const char* dimText); + void writeRadialDim(const double* centerPoint, + const double* textMidPoint, + const double* arcPoint, + const char* dimText); + void writeRadialDimBlock(const double* centerPoint, + const double* textMidPoint, + const double* arcPoint, + const char* dimText); + void writeDiametricDim(const double* textMidPoint, + const double* arcPoint1, + const double* arcPoint2, + const char* dimText); + void writeDiametricDimBlock(const double* textMidPoint, + const double* arcPoint1, + const double* arcPoint2, + const char* dimText); void writeDimBlockPreamble(); - void writeBlockTrailer(void); - - void writeHeaderSection(void); - void writeTablesSection(void); - void writeBlocksSection(void); - void writeEntitiesSection(void); - void writeObjectsSection(void); - void writeClassesSection(void); - - void makeLayerTable(void); - void makeBlockRecordTableHead(void); - void makeBlockRecordTableBody(void); - void makeBlockSectionHead(void); + void writeBlockTrailer(); + + void writeHeaderSection(); + void writeTablesSection(); + void writeBlocksSection(); + void writeEntitiesSection(); + void writeObjectsSection(); + void writeClassesSection(); + + void makeLayerTable(); + void makeBlockRecordTableHead(); + void makeBlockRecordTableBody(); + void makeBlockSectionHead(); }; +namespace DxfPrivate { + +enum class StringToErrorMode { Throw = 0x1, ReturnErrorValue = 0x2 }; + +double stringToDouble( + const std::string& line, + StringToErrorMode errorMode = StringToErrorMode::Throw +); + +int stringToInt( + const std::string& line, + StringToErrorMode errorMode = StringToErrorMode::Throw +); + +unsigned stringToUnsigned( + const std::string& line, + StringToErrorMode errorMode = StringToErrorMode::Throw +); + +} // namespace DxfPrivate + // derive a class from this and implement it's virtual functions -class CDxfRead{ +class CDxfRead +{ private: std::ifstream m_ifs; bool m_fail; - char m_str[1024]; - char m_unused_line[1024]; + std::string m_str; + std::string m_unused_line; eDxfUnits_t m_eUnits; bool m_measurement_inch; - char m_layer_name[1024]; - char m_section_name[1024]; - char m_block_name[1024]; + std::string m_layer_name; + std::string m_section_name; + std::string m_block_name; bool m_ignore_errors; + std::streamsize m_gcount = 0; + int m_line_nb = 0; + + // Mapping from layer name -> layer color index + std::unordered_map m_layer_ColorIndex_map; + const ColorIndex_t ColorBylayer = 256; + + // Map styleName to Style object + std::unordered_map m_mapStyle; - typedef std::map< std::string,Aci_t > LayerAciMap_t; - LayerAciMap_t m_layer_aci; // layer names -> layer color aci map + bool ReadInsUnits(); + bool ReadMeasurement(); + bool ReadAcadVer(); + bool ReadDwgCodePage(); - bool ReadUnits(); bool ReadLayer(); + bool ReadStyle(); bool ReadLine(); + bool ReadMText(); bool ReadText(); bool ReadArc(); bool ReadCircle(); @@ -266,49 +590,124 @@ class CDxfRead{ bool ReadSpline(); bool ReadLwPolyLine(); bool ReadPolyLine(); - bool ReadVertex(double *pVertex, bool *bulge_found, double *bulge); - void OnReadArc(double start_angle, double end_angle, double radius, const double* c, double z_extrusion_dir, bool hidden); - void OnReadCircle(const double* c, double radius, bool hidden); - void OnReadEllipse(const double* c, const double* m, double ratio, double start_angle, double end_angle); + bool ReadVertex(Dxf_VERTEX* vertex); + bool ReadSolid(); + bool ReadSection(); + bool ReadTable(); + bool ReadEndSec(); + + void OnReadArc( + double start_angle, + double end_angle, + double radius, + const DxfCoords& c, + double z_extrusion_dir, + bool hidden + ); + void OnReadCircle(const DxfCoords& c, double radius, bool hidden); + void OnReadEllipse( + const DxfCoords& c, + const DxfCoords& m, + double ratio, + double start_angle, + double end_angle + ); bool ReadInsert(); bool ReadDimension(); bool ReadBlockInfo(); - void put_line(const char *value); - void DerefACI(); + bool ResolveEncoding(); + + template + void HandleCoordCode(int n, DxfCoords* coords) + { + switch (n) { + case XCode: + coords->x = mm(DxfPrivate::stringToDouble(m_str)); + break; + case YCode: + coords->y = mm(DxfPrivate::stringToDouble(m_str)); + break; + case ZCode: + coords->z = mm(DxfPrivate::stringToDouble(m_str)); + break; + } + } + + void HandleCommonGroupCode(int n); + + void put_line(const std::string& value); + void ResolveColorIndex(); void ReportError_readInteger(const char* context); protected: - Aci_t m_aci; // manifest color name or 256 for layer color - int m_lineNum = 0; + ColorIndex_t m_ColorIndex; + eDXFVersion_t m_version; // Version from $ACADVER variable in DXF std::streamsize gcount() const; virtual void get_line(); - virtual void ReportError(const char* /*msg*/) {} + virtual void ReportError(const std::string& /*msg*/) {} + + virtual bool setSourceEncoding(const std::string& /*codepage*/) { return true; } + virtual std::string toUtf8(const std::string& strSource) { return strSource; } + +private: + std::string m_CodePage; // Code Page name from $DWGCODEPAGE or null if none/not read yet public: CDxfRead(const char* filepath); // this opens the file virtual ~CDxfRead(); // this closes the file - bool Failed(){return m_fail;} - void DoRead(const bool ignore_errors = false); // this reads the file and calls the following functions + bool IgnoreErrors() const { return m_ignore_errors; } + bool Failed() const { return m_fail; } - double mm( double value ) const; + double mm(double value) const; - bool IgnoreErrors() const { return(m_ignore_errors); } + const Dxf_STYLE* findStyle(const std::string& name) const; - virtual void OnReadLine(const double* /*s*/, const double* /*e*/, bool /*hidden*/){} - virtual void OnReadPoint(const double* /*s*/){} - virtual void OnReadText(const double* /*point*/, const double /*height*/, double /*rotation*/, const char* /*text*/){} - virtual void OnReadArc(const double* /*s*/, const double* /*e*/, const double* /*c*/, bool /*dir*/, bool /*hidden*/){} - virtual void OnReadCircle(const double* /*s*/, const double* /*c*/, bool /*dir*/, bool /*hidden*/){} - virtual void OnReadEllipse(const double* /*c*/, double /*major_radius*/, double /*minor_radius*/, double /*rotation*/, double /*start_angle*/, double /*end_angle*/, bool /*dir*/){} - virtual void OnReadSpline(struct SplineData& /*sd*/){} - virtual void OnReadInsert(const double* /*point*/, const double* /*scale*/, const char* /*name*/, double /*rotation*/){} - virtual void OnReadDimension(const double* /*s*/, const double* /*e*/, const double* /*point*/, double /*rotation*/){} - virtual void AddGraphics() const { } + void DoRead(bool ignore_errors = false); // this reads the file and calls the following functions - std::string LayerName() const; + virtual void OnReadLine(const DxfCoords& s, const DxfCoords& e, bool hidden) = 0; + + virtual void OnReadPolyline(const Dxf_POLYLINE&) = 0; + + virtual void OnReadPoint(const DxfCoords& s) = 0; + + virtual void OnReadText(const Dxf_TEXT&) = 0; + + virtual void OnReadMText(const Dxf_MTEXT&) = 0; + + virtual void OnReadArc( + const DxfCoords& s, const DxfCoords& e, const DxfCoords& c, bool dir, bool hidden + ) = 0; + virtual void OnReadCircle(const DxfCoords& s, const DxfCoords& c, bool dir, bool hidden) = 0; + + virtual void OnReadEllipse( + const DxfCoords& c, + double major_radius, + double minor_radius, + double rotation, + double start_angle, + double end_angle, + bool dir + ) = 0; + + virtual void OnReadSpline(struct SplineData& sd) = 0; + + virtual void OnReadInsert(const Dxf_INSERT& ins) = 0; + + virtual void OnReadSolid(const Dxf_SOLID& solid) = 0; + + virtual void OnReadDimension( + const DxfCoords& s, + const DxfCoords& e, + const DxfCoords& point, + double rotation + ) = 0; + + virtual void AddGraphics() const = 0; + + std::string LayerName() const; }; diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index 5d97b9ef..2bc42bfa 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -6,15 +6,18 @@ #include "io_dxf.h" +#include "../base/brep_utils.h" #include "../base/cpp_utils.h" #include "../base/document.h" #include "../base/filepath.h" #include "../base/math_utils.h" +#include "../base/mesh_utils.h" #include "../base/messenger.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" #include "../base/task_progress.h" #include "../base/string_conv.h" +#include "../base/tkernel_utils.h" #include "../base/unit_system.h" #include "aci_table.h" #include "dxf.h" @@ -24,20 +27,29 @@ #include #include #include +#include #include +#include +#include +#include +#include #include #include #include #include #include +#include #include #include #include #include +#include #include #include +#include + namespace Mayo { namespace IO { @@ -48,6 +60,15 @@ bool startsWith(std::string_view str, std::string_view prefix) return str.substr(0, prefix.size()) == prefix; } +std::string toLowerCase_C(const std::string& str) +{ + std::string lstr = str; + for (char& c : lstr) + c = std::tolower(c, std::locale::classic()); + + return lstr; +} + const Enumeration& systemFontNames() { static Enumeration fontNames; @@ -63,6 +84,11 @@ const Enumeration& systemFontNames() return fontNames; } +gp_Vec toOccVec(const DxfCoords& coords) +{ + return { coords.x, coords.y, coords.z }; +} + } // namespace class DxfReader::Internal : public CDxfRead { @@ -73,9 +99,12 @@ class DxfReader::Internal : public CDxfRead { TaskProgress* m_progress = nullptr; std::uintmax_t m_fileSize = 0; std::uintmax_t m_fileReadSize = 0; + Resource_FormatType m_srcEncoding = Resource_ANSI; protected: void get_line() override; + bool setSourceEncoding(const std::string& codepage) override; + std::string toUtf8(const std::string& strSource) override; public: Internal(const FilePath& filepath, TaskProgress* progress = nullptr); @@ -85,22 +114,26 @@ class DxfReader::Internal : public CDxfRead { const auto& layers() const { return m_layers; } // CDxfRead's virtual functions - void OnReadLine(const double* s, const double* e, bool hidden) override; - void OnReadPoint(const double* s) override; - void OnReadText(const double* point, const double height, double rotation, const char* text) override; - void OnReadArc(const double* s, const double* e, const double* c, bool dir, bool hidden) override; - void OnReadCircle(const double* s, const double* c, bool dir, bool hidden) override; - void OnReadEllipse(const double* c, double major_radius, double minor_radius, double rotation, double start_angle, double end_angle, bool dir) override; + void OnReadLine(const DxfCoords& s, const DxfCoords& e, bool hidden) override; + void OnReadPolyline(const Dxf_POLYLINE& polyline) override; + void OnReadPoint(const DxfCoords& s) override; + void OnReadText(const Dxf_TEXT& text) override; + void OnReadMText(const Dxf_MTEXT& text) override; + void OnReadArc(const DxfCoords& s, const DxfCoords& e, const DxfCoords& c, bool dir, bool hidden) override; + void OnReadCircle(const DxfCoords& s, const DxfCoords& c, bool dir, bool hidden) override; + void OnReadEllipse(const DxfCoords& c, double major_radius, double minor_radius, double rotation, double start_angle, double end_angle, bool dir) override; void OnReadSpline(struct SplineData& sd) override; - void OnReadInsert(const double* point, const double* scale, const char* name, double rotation) override; - void OnReadDimension(const double* s, const double* e, const double* point, double rotation) override; + void OnReadInsert(const Dxf_INSERT& ins) override; + void OnReadDimension(const DxfCoords& s, const DxfCoords& e, const DxfCoords& point, double rotation) override; + void OnReadSolid(const Dxf_SOLID& solid) override; - void ReportError(const char* msg) override; + void ReportError(const std::string& msg) override; + void AddGraphics() const override; static Handle_Geom_BSplineCurve createSplineFromPolesAndKnots(struct SplineData& sd); static Handle_Geom_BSplineCurve createInterpolationSpline(struct SplineData& sd); - gp_Pnt toPnt(const double* coords) const; + gp_Pnt toPnt(const DxfCoords& coords) const; void addShape(const TopoDS_Shape& shape); }; @@ -152,7 +185,7 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) Handle_XCAFDoc_ColorTool colorTool = doc->xcaf().colorTool(); Handle_XCAFDoc_LayerTool layerTool = doc->xcaf().layerTool(); std::unordered_map mapLayerNameLabel; - std::unordered_map mapAciColorLabel; + std::unordered_map mapAciColorLabel; auto fnAddRootShape = [&](const TopoDS_Shape& shape, const std::string& shapeName, TDF_Label layer) { const TDF_Label labelShape = shapeTool->NewShape(); @@ -165,7 +198,7 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) return labelShape; }; - auto fnAddAci = [&](Aci_t aci) { + auto fnAddAci = [&](ColorIndex_t aci) -> TDF_Label { auto it = mapAciColorLabel.find(aci); if (it != mapAciColorLabel.cend()) return it->second; @@ -173,7 +206,8 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) if (0 <= aci && CppUtils::cmpLess(aci, std::size(aciTable))) { const RGB_Color& c = aciTable[aci].second; const TDF_Label colorLabel = colorTool->AddColor( - Quantity_Color(c.r / 255., c.g / 255., c.b / 255., Quantity_TOC_RGB)); + Quantity_Color(c.r / 255., c.g / 255., c.b / 255., Quantity_TOC_RGB) + ); mapAciColorLabel.insert({ aci, colorLabel }); return colorLabel; } @@ -194,6 +228,12 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) progress->setValue(MathUtils::toPercent(iShape, 0, shapeCount)); }; + auto fnSetShapeColor = [=](const TDF_Label& labelShape, int aci) { + const TDF_Label labelColor = fnAddAci(aci); + if (!labelColor.IsNull()) + colorTool->SetColor(labelShape, labelColor, XCAFDoc_ColorGen); + }; + if (!m_params.groupLayers) { for (const auto& [layerName, vecEntity] : m_layers) { if (startsWith(layerName, "BLOCKS")) @@ -213,12 +253,10 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) if (startsWith(layerName, "BLOCKS")) continue; // Skip - BRep_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); + TopoDS_Compound comp = BRepUtils::makeEmptyCompound(); for (const Entity& entity : vecEntity) { if (!entity.shape.IsNull()) - builder.Add(comp, entity.shape); + BRepUtils::addShape(&comp, entity.shape); } if (!comp.IsNull()) { @@ -226,7 +264,7 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) const TDF_Label compLabel = fnAddRootShape(comp, layerName, layerLabel); // Check if all entities have the same color bool uniqueColor = true; - const Aci_t aci = !vecEntity.empty() ? vecEntity.front().aci : -1; + const ColorIndex_t aci = !vecEntity.empty() ? vecEntity.front().aci : -1; for (const Entity& entity : vecEntity) { uniqueColor = entity.aci == aci; if (!uniqueColor) @@ -234,13 +272,13 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) } if (uniqueColor) { - colorTool->SetColor(compLabel, fnAddAci(aci), XCAFDoc_ColorGen); + fnSetShapeColor(compLabel, aci); } else { for (const Entity& entity : vecEntity) { if (!entity.shape.IsNull()) { const TDF_Label entityLabel = shapeTool->AddSubShape(compLabel, entity.shape); - colorTool->SetColor(entityLabel, fnAddAci(entity.aci), XCAFDoc_ColorGen); + fnSetShapeColor(entityLabel, entity.aci); } } } @@ -278,6 +316,83 @@ void DxfReader::Internal::get_line() m_progress->setValue(MathUtils::toPercent(m_fileReadSize, 0, m_fileSize)); } +bool DxfReader::Internal::setSourceEncoding(const std::string& codepage) +{ + std::optional encoding; + +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) + if (codepage == "UTF8") + encoding = Resource_FormatType_UTF8; + else if (codepage == "ANSI_932") // Japanese + encoding = Resource_FormatType_SJIS; + else if (codepage == "ANSI_936") // UnifiedChinese + encoding = Resource_FormatType_GB; + else if (codepage == "ANSI_949") // Korean + encoding = Resource_FormatType_EUC; +#endif + +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) + if (encoding) + ; // Encoding already found above + else if (codepage == "ANSI_936") // UnifiedChinese + encoding = Resource_FormatType_GBK; + else if (codepage == "ANSI_950") // TradChinese + encoding = Resource_FormatType_Big5; + else if (codepage == "ANSI_1250") + encoding = Resource_FormatType_CP1250; + else if (codepage == "ANSI_1251") + encoding = Resource_FormatType_CP1251; + else if (codepage == "ANSI_1252") + encoding = Resource_FormatType_CP1252; + else if (codepage == "ANSI_1253") + encoding = Resource_FormatType_CP1253; + else if (codepage == "ANSI_1254") + encoding = Resource_FormatType_CP1254; + else if (codepage == "ANSI_1255") + encoding = Resource_FormatType_CP1255; + else if (codepage == "ANSI_1256") + encoding = Resource_FormatType_CP1256; + else if (codepage == "ANSI_1257") + encoding = Resource_FormatType_CP1257; + else if (codepage == "ANSI_1258") + encoding = Resource_FormatType_CP1258; +#endif + +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + if (encoding) + ; // Encoding already found above + else if (codepage == "ANSI_850" || codepage == "DOS850") + encoding = Resource_FormatType_CP850; +#endif + + if (encoding) { + m_srcEncoding = encoding.value(); + } + else { + m_srcEncoding = Resource_ANSI; + m_messenger->emitWarning("Codepage " + codepage + " not supported"); + } + + return true; +} + +std::string DxfReader::Internal::toUtf8(const std::string& strSource) +{ + if (m_srcEncoding == Resource_ANSI) // Resource_ANSI is a pass-through(OpenCascade) + return strSource; + +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) + if (m_srcEncoding == Resource_FormatType_UTF8) + return strSource; + + TCollection_ExtendedString extStr; + Resource_Unicode::ConvertFormatToUnicode(m_srcEncoding, strSource.c_str(), extStr); + return to_stdString(extStr); +#else + return strSource; +#endif +} + DxfReader::Internal::Internal(const FilePath& filepath, TaskProgress* progress) : CDxfRead(filepath.u8string().c_str()), m_progress(progress) @@ -286,7 +401,7 @@ DxfReader::Internal::Internal(const FilePath& filepath, TaskProgress* progress) m_fileSize = filepathFileSize(filepath); } -void DxfReader::Internal::OnReadLine(const double* s, const double* e, bool /*hidden*/) +void DxfReader::Internal::OnReadLine(const DxfCoords& s, const DxfCoords& e, bool /*hidden*/) { const gp_Pnt p0 = this->toPnt(s); const gp_Pnt p1 = this->toPnt(e); @@ -297,41 +412,200 @@ void DxfReader::Internal::OnReadLine(const double* s, const double* e, bool /*hi this->addShape(edge); } -void DxfReader::Internal::OnReadPoint(const double* s) +void DxfReader::Internal::OnReadPolyline(const Dxf_POLYLINE& polyline) +{ + const auto& vertices = polyline.vertices; + const bool isPolylineClosed = polyline.flags & Dxf_POLYLINE::Flag::Closed; + const int nodeCount = CppUtils::safeStaticCast(vertices.size() + (isPolylineClosed ? 1 : 0)); + MeshUtils::Polygon3dBuilder polygonBuilder(nodeCount); + for (unsigned i = 0; i < vertices.size(); ++i) + polygonBuilder.setNode(i + 1, this->toPnt(vertices.at(i).point)); + + if (isPolylineClosed) + polygonBuilder.setNode(nodeCount, this->toPnt(vertices.at(0).point)); + + polygonBuilder.finalize(); + this->addShape(BRepUtils::makeEdge(polygonBuilder.get())); +} + +void DxfReader::Internal::OnReadPoint(const DxfCoords& s) { const TopoDS_Vertex vertex = BRepBuilderAPI_MakeVertex(this->toPnt(s)); this->addShape(vertex); } -void DxfReader::Internal::OnReadText(const double* point, const double height, double rotation, const char* text) +void DxfReader::Internal::OnReadText(const Dxf_TEXT& text) { if (!m_params.importAnnotations) return; - const gp_Pnt pt = this->toPnt(point); const std::string layerName = this->LayerName(); - if (!startsWith(layerName, "BLOCKS")) { - const std::string& fontName = m_params.fontNameForTextObjects; - const double fontHeight = 4 * height * m_params.scaling; - Font_BRepFont brepFont; - if (brepFont.Init(fontName.c_str(), Font_FA_Regular, fontHeight)) { - gp_Trsf rotTrsf; - if (rotation != 0.) - rotTrsf.SetRotation(gp_Ax1(pt, gp::DZ()), UnitSystem::radians(rotation * Quantity_Degree)); - - const gp_Ax3 locText(pt, gp::DZ(), gp::DX().Transformed(rotTrsf)); - Font_BRepTextBuilder brepTextBuilder; - const TopoDS_Shape shapeText = brepTextBuilder.Perform(brepFont, text, locText); - this->addShape(shapeText); - } - else { - m_messenger->emitWarning(fmt::format("Font_BRepFont is null for '{}'", fontName)); - } + if (startsWith(layerName, "BLOCKS")) + return; + + const Dxf_STYLE* ptrStyle = this->findStyle(text.styleName); + std::string fontName = ptrStyle ? ptrStyle->name : m_params.fontNameForTextObjects; + // "ARIAL_NARROW" -> "ARIAL NARROW" + if (toLowerCase_C(fontName) == "arial_narrow") + fontName.replace(5, 1, " "); + + const double fontHeight = 1.4 * text.height * m_params.scaling; + Font_BRepFont brepFont; + brepFont.SetWidthScaling(static_cast(text.relativeXScaleFactorWidth)); + if (!brepFont.Init(fontName.c_str(), Font_FA_Regular, fontHeight/*, Font_StrictLevel_Aliases*/)) { + m_messenger->emitWarning(fmt::format("Font_BRepFont is null for '{}'", fontName)); + return; } + + using DxfHJustification = Dxf_TEXT::HorizontalJustification; + using DxfVJustification = Dxf_TEXT::VerticalJustification; + // TEXT justification is subtle(eg baseline and fit modes) + // See doc https://ezdxf.readthedocs.io/en/stable/tutorials/text.html + const DxfHJustification hjust = text.horizontalJustification; + Graphic3d_HorizontalTextAlignment hAlign = Graphic3d_HTA_LEFT; + switch (hjust) { + case DxfHJustification::Center: + case DxfHJustification::Middle: + hAlign = Graphic3d_HTA_CENTER; + break; + case DxfHJustification::Right: + hAlign = Graphic3d_HTA_RIGHT; + break; + } + + const DxfVJustification vjust = text.verticalJustification; + Graphic3d_VerticalTextAlignment vAlign = Graphic3d_VTA_TOP; + switch (vjust) { + case DxfVJustification::Baseline: + vAlign = Graphic3d_VTA_TOPFIRSTLINE; + break; + case DxfVJustification::Bottom: + vAlign = Graphic3d_VTA_BOTTOM; + break; + case DxfVJustification::Middle: + vAlign = Graphic3d_VTA_CENTER; + break; + } + + // Ensure non-null extrusion direction + gp_Vec extDir = toOccVec(text.extrusionDirection); + if (extDir.Magnitude() < gp::Resolution()) + extDir = gp::DZ(); + + // Alignment point + const bool applyFirstAlignPnt = + hjust == DxfHJustification::Left + || vjust == DxfVJustification::Baseline + ; + + const DxfCoords& alignPnt = applyFirstAlignPnt ? text.firstAlignmentPoint : text.secondAlignmentPoint; + const gp_Pnt pt = this->toPnt(alignPnt); + + gp_Vec xAxisDir = gp::DX(); + if (hjust == DxfHJustification::Aligned || hjust == DxfHJustification::Fit) { + const gp_Pnt p1 = this->toPnt(text.firstAlignmentPoint); + const gp_Pnt p2 = this->toPnt(text.secondAlignmentPoint); + xAxisDir = gp_Vec{p1, p2}; + + // Ensure non-null x-axis direction + if (xAxisDir.Magnitude() < gp::Resolution()) + xAxisDir = gp::DX(); + } + + // If rotation angle is non-null and x-axis direction defaults to standard Ox then set x-axis + // so it matches rotation angle + xAxisDir.Normalize(); + if (!MathUtils::fuzzyIsNull(text.rotationAngle) + && xAxisDir.IsEqual(gp::DX(), Precision::Confusion(), Precision::Angular())) + { + gp_Trsf rotTrsf; + rotTrsf.SetRotation(gp_Ax1(pt, gp::DZ()), text.rotationAngle); + xAxisDir = gp::DX().Transformed(rotTrsf); + } + + const gp_Ax3 locText(pt, extDir, xAxisDir); + Font_BRepTextBuilder brepTextBuilder; + const auto occTextStr = string_conv(text.str); + const TopoDS_Shape shapeText = brepTextBuilder.Perform(brepFont, occTextStr, locText, hAlign, vAlign); + this->addShape(shapeText); +} + +void DxfReader::Internal::OnReadMText(const Dxf_MTEXT& text) +{ + if (!m_params.importAnnotations) + return; + + const gp_Pnt pt = this->toPnt(text.insertionPoint); + const std::string layerName = this->LayerName(); + if (startsWith(layerName, "BLOCKS")) + return; + + const std::string& fontName = m_params.fontNameForTextObjects; + const double fontHeight = 1.4 * text.height * m_params.scaling; + Font_BRepFont brepFont; + if (!brepFont.Init(fontName.c_str(), Font_FA_Regular, fontHeight)) { + m_messenger->emitWarning(fmt::format("Font_BRepFont is null for '{}'", fontName)); + return; + } + + const int ap = static_cast(text.attachmentPoint); + Graphic3d_HorizontalTextAlignment hAlign = Graphic3d_HTA_LEFT; + if (ap == 2 || ap == 5 || ap == 8) + hAlign = Graphic3d_HTA_CENTER; + else if (ap == 3 || ap == 6 || ap == 9) + hAlign = Graphic3d_HTA_RIGHT; + + Graphic3d_VerticalTextAlignment vAlign = Graphic3d_VTA_TOP; + if (ap == 4 || ap == 5 || ap == 6) + vAlign = Graphic3d_VTA_CENTER; + else if (ap == 7 || ap == 8 || ap == 9) + vAlign = Graphic3d_VTA_BOTTOM; + + // Ensure non-null extrusion direction + gp_Vec extDir = toOccVec(text.extrusionDirection); + if (extDir.Magnitude() < gp::Resolution()) + extDir = gp::DZ(); + + // Ensure non-null x-axis direction + gp_Vec xAxisDir = toOccVec(text.xAxisDirection); + if (xAxisDir.Magnitude() < gp::Resolution()) + xAxisDir = gp::DX(); + + // If rotation angle is non-null and x-axis direction defaults to standard Ox then set x-axis + // so it matches rotation angle + xAxisDir.Normalize(); + if (!MathUtils::fuzzyIsNull(text.rotationAngle) + && xAxisDir.IsEqual(gp::DX(), Precision::Confusion(), Precision::Angular())) + { + gp_Trsf rotTrsf; + rotTrsf.SetRotation(gp_Ax1(pt, gp::DZ()), text.rotationAngle); + xAxisDir = gp::DX().Transformed(rotTrsf); + } + + const auto occTextStr = string_conv(text.str); + const gp_Ax3 locText(pt, extDir, xAxisDir); + Font_BRepTextBuilder brepTextBuilder; +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) + OccHandle textFormat = new Font_TextFormatter; + textFormat->SetupAlignment(hAlign, vAlign); + textFormat->Append(occTextStr, *brepFont.FTFont()); + /* Font_TextFormatter computes weird ResultWidth() so wrapping is currently broken + if (text.acadHasColumnInfo && text.acadColumnInfo_Width > 0.) { + textFormat->SetWordWrapping(true); + textFormat->SetWrapping(text.acadColumnInfo_Width); + } + */ + textFormat->Format(); + const TopoDS_Shape shapeText = brepTextBuilder.Perform(brepFont, textFormat, locText); +#else + const TopoDS_Shape shapeText = brepTextBuilder.Perform(brepFont, occTextStr, locText, hAlign, vAlign); +#endif + + this->addShape(shapeText); } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -void DxfReader::Internal::OnReadArc(const double* s, const double* e, const double* c, bool dir, bool /*hidden*/) +void DxfReader::Internal::OnReadArc(const DxfCoords& s, const DxfCoords& e, const DxfCoords& c, bool dir, bool /*hidden*/) { const gp_Pnt p0 = this->toPnt(s); const gp_Pnt p1 = this->toPnt(e); @@ -348,7 +622,7 @@ void DxfReader::Internal::OnReadArc(const double* s, const double* e, const doub } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -void DxfReader::Internal::OnReadCircle(const double* s, const double* c, bool dir, bool /*hidden*/) +void DxfReader::Internal::OnReadCircle(const DxfCoords& s, const DxfCoords& c, bool dir, bool /*hidden*/) { const gp_Pnt p0 = this->toPnt(s); const gp_Dir up = dir ? gp::DZ() : -gp::DZ(); @@ -365,11 +639,12 @@ void DxfReader::Internal::OnReadCircle(const double* s, const double* c, bool di // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf void DxfReader::Internal::OnReadEllipse( - const double* c, + const DxfCoords& c, double major_radius, double minor_radius, double rotation, double /*start_angle*/, double /*end_angle*/, - bool dir) + bool dir + ) { const gp_Dir up = dir ? gp::DZ() : -gp::DZ(); const gp_Pnt pc = this->toPnt(c); @@ -413,67 +688,123 @@ void DxfReader::Internal::OnReadSpline(SplineData& sd) } // Excerpted from FreeCad/src/Mod/Import/App/ImpExpDxf -void DxfReader::Internal::OnReadInsert(const double* point, const double* scale, const char* name, double rotation) +void DxfReader::Internal::OnReadInsert(const Dxf_INSERT& ins) { - //std::cout << "Inserting block " << name << " rotation " << rotation << " pos " << point[0] << "," << point[1] << "," << point[2] << " scale " << scale[0] << "," << scale[1] << "," << scale[2] << std::endl; - const std::string prefix = std::string("BLOCKS ") + name + " "; + const std::string prefix = "BLOCKS " + ins.blockName + " "; for (const auto& [k, vecEntity] : m_layers) { if (!startsWith(k, prefix)) continue; // Skip - BRep_Builder builder; - TopoDS_Compound comp; - builder.MakeCompound(comp); + TopoDS_Shape comp = BRepUtils::makeEmptyCompound(); for (const DxfReader::Entity& entity : vecEntity) { if (!entity.shape.IsNull()) - builder.Add(comp, entity.shape); + BRepUtils::addShape(&comp, entity.shape); } if (comp.IsNull()) continue; // Skip - auto nonNull = [](double v) { return !MathUtils::fuzzyIsNull(v) ? v : 1.; }; - const double nscale[] = { nonNull(scale[0]), nonNull(scale[1]), nonNull(scale[2]) }; - gp_Trsf trsfScale; - trsfScale.SetValues( - nscale[0], 0, 0, 0, - 0, nscale[1], 0, 0, - 0, 0, nscale[2], 0); + if (!MathUtils::fuzzyEqual(ins.scaleFactor.x, ins.scaleFactor.y) + || !MathUtils::fuzzyEqual(ins.scaleFactor.x, ins.scaleFactor.y) + ) + { + m_messenger->emitWarning( + fmt::format("OnReadInsert('{}') - non-uniform scales aren't supported({}, {}, {})", + ins.blockName, ins.scaleFactor.x, ins.scaleFactor.y, ins.scaleFactor.z + ) + ); + } + + auto fnNonNull = [](double v) { return !MathUtils::fuzzyIsNull(v) ? v : 1.; }; + const double avgScale = std::abs(fnNonNull((ins.scaleFactor.x + ins.scaleFactor.y + ins.scaleFactor.z) / 3.)); + if (!MathUtils::fuzzyEqual(avgScale, 1.)) { + gp_Trsf trsf; + trsf.SetScaleFactor(avgScale); + BRepBuilderAPI_Transform brepTrsf(comp, trsf); + if (brepTrsf.IsDone()) { + comp = brepTrsf.Shape(); + } + else { + m_messenger->emitWarning( + fmt::format("OnReadInsert('{}') - scaling failed({}, {}, {})", + ins.blockName, ins.scaleFactor.x, ins.scaleFactor.y, ins.scaleFactor.z + ) + ); + } + } + gp_Trsf trsfRotZ; - trsfRotZ.SetRotation(gp::OZ(), rotation); + if (!MathUtils::fuzzyIsNull(ins.rotationAngle)) + trsfRotZ.SetRotation(gp::OZ(), ins.rotationAngle); + gp_Trsf trsfMove; - trsfMove.SetTranslation(this->toPnt(point).XYZ()); - const gp_Trsf trsf = trsfScale * trsfRotZ * trsfMove; - comp.Location(trsf); + trsfMove.SetTranslation(this->toPnt(ins.insertPoint).XYZ()); + + comp.Location(trsfRotZ * trsfMove); this->addShape(comp); } } -void DxfReader::Internal::OnReadDimension(const double* s, const double* e, const double* point, double rotation) +void DxfReader::Internal::OnReadDimension(const DxfCoords& s, const DxfCoords& e, const DxfCoords& point, double rotation) { if (m_params.importAnnotations) { // TODO std::stringstream sstr; sstr << "DxfReader::OnReadDimension() - Not yet implemented" << std::endl - << " s: " << s[0] << ", " << s[1] << ", " << s[2] << std::endl - << " e: " << e[0] << ", " << e[1] << ", " << e[2] << std::endl - << " point: " << point[0] << ", " << point[1] << ", " << point[2] << std::endl + << " s: " << s.x << ", " << s.y << ", " << s.z << std::endl + << " e: " << e.x << ", " << e.y << ", " << e.z << std::endl + << " point: " << point.x << ", " << point.y << ", " << point.z << std::endl << " rotation: " << rotation << std::endl; m_messenger->emitWarning(sstr.str()); } } -void DxfReader::Internal::ReportError(const char* msg) +void DxfReader::Internal::OnReadSolid(const Dxf_SOLID& solid) +{ + const gp_Pnt p1 = this->toPnt(solid.corner1); + const gp_Pnt p2 = this->toPnt(solid.corner2); + const gp_Pnt p3 = this->toPnt(solid.corner3); + const gp_Pnt p4 = this->toPnt(solid.corner4); + + TopoDS_Face face; + try { + BRepBuilderAPI_MakeWire makeWire; + makeWire.Add(BRepBuilderAPI_MakeEdge(p1, p2)); + if (solid.hasCorner4 && !p3.IsEqual(p4, Precision::Confusion())) { + makeWire.Add(BRepBuilderAPI_MakeEdge(p2, p4)); + makeWire.Add(BRepBuilderAPI_MakeEdge(p4, p3)); + } + else { + makeWire.Add(BRepBuilderAPI_MakeEdge(p2, p3)); + } + + makeWire.Add(BRepBuilderAPI_MakeEdge(p3, p1)); + if (makeWire.IsDone()) + face = BRepBuilderAPI_MakeFace(makeWire.Wire(), true/*onlyPlane*/); + } catch (...) { + m_messenger->emitError("OnReadSolid() failed"); + } + + if (!face.IsNull()) + this->addShape(face); +} + +void DxfReader::Internal::ReportError(const std::string& msg) { m_messenger->emitError(msg); } -gp_Pnt DxfReader::Internal::toPnt(const double* coords) const +void DxfReader::Internal::AddGraphics() const +{ + // Nothing +} + +gp_Pnt DxfReader::Internal::toPnt(const DxfCoords& coords) const { - double sp1(coords[0]); - double sp2(coords[1]); - double sp3(coords[2]); - if (m_params.scaling != 1.0) { + double sp1(coords.x); + double sp2(coords.y); + double sp3(coords.z); + if (!MathUtils::fuzzyEqual(m_params.scaling, 1.)) { sp1 = sp1 * m_params.scaling; sp2 = sp2 * m_params.scaling; sp3 = sp3 * m_params.scaling; @@ -484,7 +815,7 @@ gp_Pnt DxfReader::Internal::toPnt(const double* coords) const void DxfReader::Internal::addShape(const TopoDS_Shape& shape) { - const Entity newEntity{ m_aci, shape }; + const Entity newEntity{ m_ColorIndex, shape }; const std::string layerName = this->LayerName(); auto itFound = m_layers.find(layerName); if (itFound != m_layers.end()) { diff --git a/src/io_gmio/io_gmio.cpp b/src/io_gmio/io_gmio.cpp index 79a5536c..e29c2858 100644 --- a/src/io_gmio/io_gmio.cpp +++ b/src/io_gmio/io_gmio.cpp @@ -8,6 +8,8 @@ #include "io_gmio_amf_writer.h" +#include + namespace Mayo { namespace IO { @@ -34,5 +36,10 @@ GmioFactoryWriter::createProperties(Format format, PropertyGroup* parentGroup) c return {}; } +std::string_view GmioLib::strVersion() +{ + return GMIO_VERSION_STR; +} + } // namespace IO } // namespace Mayo diff --git a/src/io_gmio/io_gmio.h b/src/io_gmio/io_gmio.h index 722f78c1..5ec737c0 100644 --- a/src/io_gmio/io_gmio.h +++ b/src/io_gmio/io_gmio.h @@ -8,6 +8,7 @@ #include "../base/io_writer.h" #include "../base/property.h" + #include namespace Mayo { @@ -29,5 +30,16 @@ class GmioFactoryWriter : public FactoryWriter { } }; +struct GmioLib { + static std::string_view strName() { return "gmio"; } +#ifdef HAVE_GMIO + static std::string_view strVersion(); + static std::string_view strVersionDetails() { return "(build)"; } +#else + static std::string_view strVersion() { return ""; } + static std::string_view strVersionDetails() { return ""; } +#endif +}; + } // namespace IO } // namespace Mayo diff --git a/src/io_gmio/io_gmio_amf_writer.cpp b/src/io_gmio/io_gmio_amf_writer.cpp index 4dc5faca..8c2d0199 100644 --- a/src/io_gmio/io_gmio_amf_writer.cpp +++ b/src/io_gmio/io_gmio_amf_writer.cpp @@ -157,7 +157,8 @@ class GmioAmfWriter::Properties : public PropertyGroup { this->createZipArchive.label())); } - void restoreDefaults() override { + void restoreDefaults() override + { const GmioAmfWriter::Parameters params; this->float64Format.setValue(params.float64Format); this->float64Precision.setValue(params.float64Precision); @@ -351,7 +352,7 @@ int GmioAmfWriter::createObject(const TDF_Label& labelShape) return mat.color == color; }); if (itColor != m_vecMaterial.cend()) { - materialId = itColor - m_vecMaterial.cbegin(); + materialId = CppUtils::safeStaticCast(itColor - m_vecMaterial.cbegin()); } else { materialId = CppUtils::safeStaticCast(m_vecMaterial.size()); diff --git a/src/io_image/io_image.cpp b/src/io_image/io_image.cpp index e1e071d2..a8dc7731 100644 --- a/src/io_image/io_image.cpp +++ b/src/io_image/io_image.cpp @@ -58,6 +58,7 @@ class ImageWriter::Properties : public PropertyGroup { this->cameraOrientation.setDescription( ImageWriterI18N::textIdTr("Camera orientation expressed in Z-up convention as a unit vector")); + this->cameraProjection.mutableEnumeration().changeTrContext(ImageWriterI18N::textIdContext()); } void restoreDefaults() override { diff --git a/src/io_image/io_image.h b/src/io_image/io_image.h index 80770999..66b70076 100644 --- a/src/io_image/io_image.h +++ b/src/io_image/io_image.h @@ -29,6 +29,10 @@ class GuiDocument; namespace Mayo { namespace IO { +// Provides a writer for image creation +// Formats are those supported by OpenCascade with Image_AlienPixMap, see: +// https://dev.opencascade.org/doc/refman/html/class_image___alien_pix_map.html#details +// The image format is specified with the extension for the target file path(eg .png, .jpeg, ...) class ImageWriter : public Writer { public: ImageWriter(GuiApplication* guiApp); diff --git a/src/io_occ/io_occ_brep.cpp b/src/io_occ/io_occ_brep.cpp index aa5c9193..f39c7ec3 100644 --- a/src/io_occ/io_occ_brep.cpp +++ b/src/io_occ/io_occ_brep.cpp @@ -7,10 +7,12 @@ #include "io_occ_brep.h" #include "../base/application_item.h" +#include "../base/brep_utils.h" #include "../base/caf_utils.h" #include "../base/document.h" #include "../base/filepath_conv.h" #include "../base/occ_progress_indicator.h" +#include "../base/io_system.h" #include "../base/task_progress.h" #include "../base/tkernel_utils.h" @@ -52,7 +54,7 @@ bool OccBRepWriter::transfer(Span appItems, TaskProgress* std::vector vecShape; vecShape.reserve(appItems.size()); - for (const ApplicationItem& item : appItems) { + System::visitUniqueItems(appItems, [&](const ApplicationItem& item) { if (item.isDocument()) { for (const TDF_Label& label : item.document()->xcaf().topLevelFreeShapes()) vecShape.push_back(XCaf::shape(label)); @@ -61,16 +63,12 @@ bool OccBRepWriter::transfer(Span appItems, TaskProgress* const TDF_Label labelNode = item.documentTreeNode().label(); vecShape.push_back(XCaf::shape(labelNode)); } - } + }); if (vecShape.size() > 1) { - TopoDS_Compound cmpd; - BRep_Builder builder; - builder.MakeCompound(cmpd); + m_shape = BRepUtils::makeEmptyCompound(); for (const TopoDS_Shape& subShape : vecShape) - builder.Add(cmpd, subShape); - - m_shape = cmpd; + BRepUtils::addShape(&m_shape, subShape); } else if (vecShape.size() == 1) { m_shape = vecShape.front(); diff --git a/src/io_occ/io_occ_caf.cpp b/src/io_occ/io_occ_caf.cpp index bd522d4c..269347da 100644 --- a/src/io_occ/io_occ_caf.cpp +++ b/src/io_occ/io_occ_caf.cpp @@ -8,6 +8,7 @@ #include "../base/global.h" #include "../base/document.h" #include "../base/occ_progress_indicator.h" +#include "../base/io_system.h" #include "../base/task_progress.h" #include "../base/tkernel_utils.h" @@ -58,7 +59,11 @@ bool cafGenericWriteTransfer(CafWriterType& writer, Span auto _ = gsl::finally([&]{ Private::cafFinderProcess(writer)->SetProgress(nullptr); }); #endif - for (const ApplicationItem& item : appItems) { + bool okTransfer = true; + System::visitUniqueItems(appItems, [&](const ApplicationItem& item) { + if (!okTransfer) + return; // Skip if already in error state + bool okItemTransfer = false; if (item.isDocument()) okItemTransfer = writer.Transfer(item.document()); @@ -66,10 +71,10 @@ bool cafGenericWriteTransfer(CafWriterType& writer, Span okItemTransfer = writer.Transfer(item.documentTreeNode().label()); if (!okItemTransfer) - return false; - } + okTransfer = false; + }); - return true; + return okTransfer; } } // namespace diff --git a/src/io_occ/io_occ_gltf_writer.cpp b/src/io_occ/io_occ_gltf_writer.cpp index 08aa4b47..ca6da673 100644 --- a/src/io_occ/io_occ_gltf_writer.cpp +++ b/src/io_occ/io_occ_gltf_writer.cpp @@ -8,6 +8,7 @@ #include "../base/application_item.h" #include "../base/enumeration_fromenum.h" +#include "../base/io_system.h" #include "../base/messenger.h" #include "../base/occ_progress_indicator.h" #include "../base/property_builtins.h" @@ -45,6 +46,8 @@ class OccGltfWriter::Properties : public PropertyGroup { "vector, Rotation quaternion and Scale factor(T * R * S)") } }); + this->format.mutableEnumeration().changeTrContext(OccGltfWriter::Properties::textIdContext()); + this->nodeNameFormat.setDescription(textIdTr("Name format for exporting nodes")); this->meshNameFormat.setDescription(textIdTr("Name format for exporting meshes")); this->embedTextures.setDescription( @@ -117,7 +120,7 @@ bool OccGltfWriter::transfer(Span spanAppItem, TaskProgre { m_document.Nullify(); m_seqRootLabel.Clear(); - for (const ApplicationItem& appItem : spanAppItem) { + System::visitUniqueItems(spanAppItem, [=](const ApplicationItem& appItem) { if (appItem.isDocument() && m_document.IsNull()) { m_document = appItem.document(); } @@ -128,7 +131,7 @@ bool OccGltfWriter::transfer(Span spanAppItem, TaskProgre if (appItem.document().get() == m_document.get()) m_seqRootLabel.Append(appItem.documentTreeNode().label()); } - } + }); if (!m_document) return false; diff --git a/src/io_occ/io_occ_iges.cpp b/src/io_occ/io_occ_iges.cpp index c41a5811..ac469f06 100644 --- a/src/io_occ/io_occ_iges.cpp +++ b/src/io_occ/io_occ_iges.cpp @@ -31,7 +31,9 @@ class OccIgesReader::Properties : public PropertyGroup { "This parameter does not change the continuity of curves that are used " "in the construction of IGES BRep entities. In this case, the parameter " "does not influence the continuity of the resulting Open CASCADE curves " - "(it is ignored).")); + "(it is ignored)." + ) + ); this->surfaceCurveMode.setDescription( textIdTr("Preference for the computation of curves in case of 2D/3D inconsistency " @@ -49,7 +51,9 @@ class OccIgesReader::Properties : public PropertyGroup { "of sub-curves given in the IGES file or because of splitting of curves during " "translation\n" "- 3D or 2D curve is a Circular Arc (entity type 100) starting and ending " - "in the same point (note that this case is incorrect according to the IGES standard)")); + "in the same point (note that this case is incorrect according to the IGES standard)" + ) + ); this->readFaultyEntities.setDescription(textIdTr("Read failed entities")); diff --git a/src/io_occ/io_occ_obj_writer.cpp b/src/io_occ/io_occ_obj_writer.cpp index 5ffdd300..30123691 100644 --- a/src/io_occ/io_occ_obj_writer.cpp +++ b/src/io_occ/io_occ_obj_writer.cpp @@ -7,6 +7,7 @@ #include "io_occ_obj_writer.h" #include "../base/application_item.h" +#include "../base/io_system.h" #include "../base/occ_progress_indicator.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" @@ -43,7 +44,7 @@ bool OccObjWriter::transfer(Span spanAppItem, TaskProgres { m_document.Nullify(); m_seqRootLabel.Clear(); - for (const ApplicationItem& appItem : spanAppItem) { + System::visitUniqueItems(spanAppItem, [=](const ApplicationItem& appItem) { if (appItem.isDocument() && m_document.IsNull()) { m_document = appItem.document(); } @@ -54,7 +55,7 @@ bool OccObjWriter::transfer(Span spanAppItem, TaskProgres if (appItem.document().get() == m_document.get()) m_seqRootLabel.Append(appItem.documentTreeNode().label()); } - } + }); if (!m_document) return false; diff --git a/src/io_occ/io_occ_step.cpp b/src/io_occ/io_occ_step.cpp index 0cc929cb..85b343af 100644 --- a/src/io_occ/io_occ_step.cpp +++ b/src/io_occ/io_occ_step.cpp @@ -6,6 +6,7 @@ #include "io_occ_step.h" #include "io_occ_caf.h" +#include "../base/messenger.h" #include "../base/meta_enum.h" #include "../base/occ_static_variables_rollback.h" #include "../base/property_builtins.h" @@ -329,14 +330,10 @@ bool OccStepWriter::writeFile(const FilePath& filepath, TaskProgress* /*progress this->changeStaticVariables(&rollback); APIHeaderSection_MakeHeader makeHeader(m_writer->ChangeWriter().Model()); - makeHeader.SetAuthorValue( - 1, string_conv(m_params.headerAuthor)); - makeHeader.SetOrganizationValue( - 1, string_conv(m_params.headerOrganization)); - makeHeader.SetOriginatingSystem( - string_conv(m_params.headerOriginatingSystem)); - makeHeader.SetDescriptionValue( - 1, string_conv(m_params.headerDescription)); + makeHeader.SetAuthorValue(1, to_OccHandleHAsciiString(m_params.headerAuthor)); + makeHeader.SetOrganizationValue(1, to_OccHandleHAsciiString(m_params.headerOrganization)); + makeHeader.SetOriginatingSystem(to_OccHandleHAsciiString(m_params.headerOriginatingSystem)); + makeHeader.SetDescriptionValue(1, to_OccHandleHAsciiString(m_params.headerDescription)); const IFSelect_ReturnStatus err = m_writer->Write(filepath.u8string().c_str()); return err == IFSelect_RetDone; diff --git a/src/io_occ/io_occ_stl.cpp b/src/io_occ/io_occ_stl.cpp index c61697c9..869bd3b8 100644 --- a/src/io_occ/io_occ_stl.cpp +++ b/src/io_occ/io_occ_stl.cpp @@ -12,6 +12,8 @@ #include "../base/triangulation_annex_data.h" #include "../base/document.h" #include "../base/filepath_conv.h" +#include "../base/global.h" +#include "../base/io_system.h" #include "../base/messenger.h" #include "../base/occ_progress_indicator.h" #include "../base/property_enumeration.h" @@ -38,11 +40,9 @@ static TopoDS_Shape asShape(const DocumentPtr& doc) shape = XCaf::shape(doc->entityLabel(0)); } else if (doc->entityCount() > 1) { - TopoDS_Compound cmpd; - BRep_Builder builder; - builder.MakeCompound(cmpd); + TopoDS_Compound cmpd = BRepUtils::makeEmptyCompound(); for (int i = 0; i < doc->entityCount(); ++i) - builder.Add(cmpd, XCaf::shape(doc->entityLabel(i))); + BRepUtils::addShape(&cmpd, XCaf::shape(doc->entityLabel(i))); shape = cmpd; } @@ -93,21 +93,17 @@ TDF_LabelSequence OccStlReader::transfer(DocumentPtr doc, TaskProgress* /*progre bool OccStlWriter::transfer(Span appItems, TaskProgress* /*progress*/) { -// if (appItems.size() > 1) -// return Result::error(tr("OpenCascade RWStl does not support multi-solids")); - - m_shape = {}; - if (!appItems.empty()) { - const ApplicationItem& item = appItems.front(); - if (item.isDocument()) { - m_shape = asShape(item.document()); + m_shape = BRepUtils::makeEmptyCompound(); + System::visitUniqueItems(appItems, [=](const ApplicationItem& appItem) { + if (appItem.isDocument()) { + BRepUtils::addShape(&m_shape, asShape(appItem.document())); } - else if (item.isDocumentTreeNode()) { - const TDF_Label label = item.documentTreeNode().label(); + else if (appItem.isDocumentTreeNode()) { + const TDF_Label label = appItem.documentTreeNode().label(); if (XCaf::isShape(label)) - m_shape = XCaf::shape(label); + BRepUtils::addShape(&m_shape, XCaf::shape(label)); } - } + }); return !m_shape.IsNull(); } @@ -138,6 +134,7 @@ bool OccStlWriter::writeFile(const FilePath& filepath, TaskProgress* progress) Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); return writer.Write(m_shape, strFilepath.c_str(), TKernelUtils::start(indicator)); #else + MAYO_UNUSED(progress); return writer.Write(m_shape, strFilepath.c_str()); #endif } diff --git a/src/io_occ/io_occ_vrml_writer.cpp b/src/io_occ/io_occ_vrml_writer.cpp index 61ca2906..c105938f 100644 --- a/src/io_occ/io_occ_vrml_writer.cpp +++ b/src/io_occ/io_occ_vrml_writer.cpp @@ -9,6 +9,7 @@ #include "../base/application_item.h" #include "../base/caf_utils.h" #include "../base/document.h" +#include "../base/io_system.h" #include "../base/math_utils.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" @@ -46,12 +47,17 @@ bool OccVrmlWriter::transfer(Span spanAppItem, TaskProgre { m_scene.reset(new VrmlData_Scene); VrmlData_ShapeConvert converter(*m_scene); - for (const ApplicationItem& appItem : spanAppItem) { + + int count = 0; + System::visitUniqueItems(spanAppItem, [&](const ApplicationItem&) { ++count; }); + + int iCount = 0; + System::visitUniqueItems(spanAppItem, [&](const ApplicationItem& appItem) { if (appItem.isDocument()) { #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) converter.ConvertDocument(appItem.document()); #else - // TODO Call VrmlData_ShapeConvert::AddShape() on each child entity of "shape" type + // TODO Call VrmlData_ShapeConvert::AddShape() on each child entity of "shape" type #endif } else if (appItem.isDocumentTreeNode()) { @@ -60,9 +66,8 @@ bool OccVrmlWriter::transfer(Span spanAppItem, TaskProgre converter.AddShape(XCaf::shape(label)); } - const auto index = &appItem - &spanAppItem.front(); - progress->setValue(MathUtils::toPercent(index, 0, spanAppItem.size() - 1)); - } + progress->setValue(MathUtils::toPercent(++iCount, 0, count)); + }); const auto rep = m_shapeRepresentation; converter.Convert( diff --git a/src/io_off/io_off_reader.cpp b/src/io_off/io_off_reader.cpp index 2adffc09..2c87f7ee 100644 --- a/src/io_off/io_off_reader.cpp +++ b/src/io_off/io_off_reader.cpp @@ -319,7 +319,7 @@ TDF_Label OffReader::transferMesh(DocumentPtr doc, TaskProgress* progress) std::vector vecVertexColor; vecVertexColor.reserve(m_vecVertex.size()); for (const Vertex& vertex : m_vecVertex) { - const auto ivertex = &vertex - &m_vecVertex.front(); + const auto ivertex = Span_itemIndex(m_vecVertex, vertex); MeshUtils::setNode(mesh, ivertex + 1, vertex.coords); const std::uint32_t c = vertex.color; if (vertex.hasColor) { diff --git a/src/io_off/io_off_reader.h b/src/io_off/io_off_reader.h index eba56187..c5e319f9 100644 --- a/src/io_off/io_off_reader.h +++ b/src/io_off/io_off_reader.h @@ -11,7 +11,6 @@ #include #include - #include namespace Mayo { diff --git a/src/io_ply/io_ply_reader.cpp b/src/io_ply/io_ply_reader.cpp index 50798dbe..67930b10 100644 --- a/src/io_ply/io_ply_reader.cpp +++ b/src/io_ply/io_ply_reader.cpp @@ -202,7 +202,9 @@ TDF_Label PlyReader::transferPointCloud(DocumentPtr doc, TaskProgress* /*progres { const bool hasColors = !m_vecColorComponent.empty(); const bool hasNormals = false; //!m_vecNormalCoord.empty(); - auto gfxPoints = new Graphic3d_ArrayOfPoints(m_vecNodeCoord.size(), hasColors, hasNormals); + auto gfxPoints = new Graphic3d_ArrayOfPoints( + CppUtils::safeStaticCast(m_vecNodeCoord.size()), hasColors, hasNormals + ); // Add nodes(vertices) into point cloud for (int i = 0; CppUtils::cmpLess(i, m_vecNodeCoord.size()); i += 3) { diff --git a/src/measure/measure_display.cpp b/src/measure/measure_display.cpp index ade95d89..62f24d63 100644 --- a/src/measure/measure_display.cpp +++ b/src/measure/measure_display.cpp @@ -36,13 +36,14 @@ std::unique_ptr BaseMeasureDisplay::createFrom(MeasureType type case MeasureType::CircleDiameter: return std::make_unique(std::get(value)); case MeasureType::MinDistance: - return std::make_unique(std::get(value)); + case MeasureType::CenterDistance: + return std::make_unique(std::get(value)); case MeasureType::Length: - return std::make_unique(std::get(value)); + return std::make_unique(std::get(value)); case MeasureType::Angle: return std::make_unique(std::get(value)); case MeasureType::Area: - return std::make_unique(std::get(value)); + return std::make_unique(std::get(value)); default: return {}; } @@ -52,9 +53,9 @@ std::unique_ptr BaseMeasureDisplay::createEmptySumFrom(MeasureT { switch (type) { case MeasureType::Length: - return std::make_unique(Mayo::QuantityLength{0}); + return std::make_unique(Mayo::MeasureLength{}); case MeasureType::Area: - return std::make_unique(Mayo::QuantityArea{0}); + return std::make_unique(Mayo::MeasureArea{}); default: return {}; } @@ -264,7 +265,7 @@ gp_Pnt MeasureDisplayCircleDiameter::diameterOpposedPnt(const gp_Pnt& pntOnCircl // -- MinDistance // -- -MeasureDisplayMinDistance::MeasureDisplayMinDistance(const MeasureMinDistance& dist) +MeasureDisplayDistance::MeasureDisplayDistance(const MeasureDistance& dist) : m_dist(dist), m_gfxLength(new AIS_Line(new Geom_CartesianPoint(dist.pnt1), new Geom_CartesianPoint(dist.pnt2))), m_gfxDistText(new AIS_TextLabel), @@ -277,12 +278,29 @@ MeasureDisplayMinDistance::MeasureDisplayMinDistance(const MeasureMinDistance& d BaseMeasureDisplay::applyGraphicsDefaults(this); } -void MeasureDisplayMinDistance::update(const MeasureDisplayConfig& config) +void MeasureDisplayDistance::update(const MeasureDisplayConfig& config) { const auto trLength = UnitSystem::translateLength(m_dist.value, config.lengthUnit); const auto strLength = BaseMeasureDisplay::text(trLength, config); + + std::string distStr; + switch(m_dist.type) + { + case DistanceType::Mininmum: + distStr = "Min Distance"; + break; + case DistanceType::CenterToCenter: + distStr = "Distance"; + break; + default: + distStr = "Distance"; + break; + } + + distStr += ": {0}{1}
Point1: {2}
Point2: {3}"; + this->setText(fmt::format( - MeasureDisplayI18N::textIdTr("Min Distance: {0}{1}
Point1: {2}
Point2: {3}"), + MeasureDisplayI18N::textIdTr(distStr.c_str()), strLength, trLength.strUnit, BaseMeasureDisplay::text(m_dist.pnt1, config), @@ -292,7 +310,7 @@ void MeasureDisplayMinDistance::update(const MeasureDisplayConfig& config) BaseMeasureDisplay::adaptScale(m_gfxDistText, config); } -GraphicsObjectPtr MeasureDisplayMinDistance::graphicsObjectAt(int i) const +GraphicsObjectPtr MeasureDisplayDistance::graphicsObjectAt(int i) const { switch (i) { case 0: return m_gfxLength; @@ -358,26 +376,37 @@ GraphicsObjectPtr MeasureDisplayAngle::graphicsObjectAt(int i) const // -- Length // -- -MeasureDisplayLength::MeasureDisplayLength(QuantityLength length) - : m_length(length) +MeasureDisplayLength::MeasureDisplayLength(const MeasureLength& length) + : m_length(length), + m_gfxLenText(new AIS_TextLabel) { + m_gfxLenText->SetPosition(length.middlePnt); + BaseMeasureDisplay::applyGraphicsDefaults(this); } void MeasureDisplayLength::update(const MeasureDisplayConfig& config) { - const auto trLength = UnitSystem::translateLength(m_length, config.lengthUnit); + const auto trLength = UnitSystem::translateLength(m_length.value, config.lengthUnit); + const auto strLength = BaseMeasureDisplay::text(trLength, config); this->setText(fmt::format( MeasureDisplayI18N::textIdTr("{0}: {1}{2}"), BaseMeasureDisplay::sumTextOr(MeasureDisplayI18N::textIdTr("Length")), - BaseMeasureDisplay::text(trLength, config), + strLength, trLength.strUnit )); + m_gfxLenText->SetText(to_OccExtString(" " + strLength)); + BaseMeasureDisplay::adaptScale(m_gfxLenText, config); +} + +GraphicsObjectPtr MeasureDisplayLength::graphicsObjectAt(int i) const +{ + return i == 0 ? m_gfxLenText : GraphicsObjectPtr{}; } void MeasureDisplayLength::sumAdd(const IMeasureDisplay& other) { const auto& otherLen = dynamic_cast(other); - m_length += otherLen.m_length; + m_length.value += otherLen.m_length.value; BaseMeasureDisplay::sumAdd(other); } @@ -385,26 +414,37 @@ void MeasureDisplayLength::sumAdd(const IMeasureDisplay& other) // -- Area // -- -MeasureDisplayArea::MeasureDisplayArea(QuantityArea area) - : m_area(area) +MeasureDisplayArea::MeasureDisplayArea(const MeasureArea& area) + : m_area(area), + m_gfxAreaText(new AIS_TextLabel) { + m_gfxAreaText->SetPosition(area.middlePnt); + BaseMeasureDisplay::applyGraphicsDefaults(this); } void MeasureDisplayArea::update(const MeasureDisplayConfig& config) { - const auto trArea = UnitSystem::translateArea(m_area, config.areaUnit); + const auto trArea = UnitSystem::translateArea(m_area.value, config.areaUnit); + const auto strArea = BaseMeasureDisplay::text(trArea, config); this->setText(fmt::format( MeasureDisplayI18N::textIdTr("{0}: {1}{2}"), BaseMeasureDisplay::sumTextOr(MeasureDisplayI18N::textIdTr("Area")), - BaseMeasureDisplay::text(trArea, config), + strArea, trArea.strUnit )); + m_gfxAreaText->SetText(to_OccExtString(" " + strArea)); + BaseMeasureDisplay::adaptScale(m_gfxAreaText, config); +} + +GraphicsObjectPtr MeasureDisplayArea::graphicsObjectAt(int i) const +{ + return i == 0 ? m_gfxAreaText : GraphicsObjectPtr{}; } void MeasureDisplayArea::sumAdd(const IMeasureDisplay& other) { const auto& otherArea = dynamic_cast(other); - m_area += otherArea.m_area; + m_area.value += otherArea.m_area.value; BaseMeasureDisplay::sumAdd(other); } diff --git a/src/measure/measure_display.h b/src/measure/measure_display.h index b5573c5c..23aa0be5 100644 --- a/src/measure/measure_display.h +++ b/src/measure/measure_display.h @@ -24,6 +24,7 @@ namespace Mayo { +// Provides parameters to configure IMeasureDisplay objects struct MeasureDisplayConfig { LengthUnit lengthUnit = LengthUnit::Millimeter; AngleUnit angleUnit = AngleUnit::Degree; @@ -155,15 +156,15 @@ class MeasureDisplayCircleDiameter : public BaseMeasureDisplay { // -- MinDistance // -- -class MeasureDisplayMinDistance : public BaseMeasureDisplay { +class MeasureDisplayDistance : public BaseMeasureDisplay { public: - MeasureDisplayMinDistance(const MeasureMinDistance& dist); + MeasureDisplayDistance(const MeasureDistance& dist); void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override { return 4; } GraphicsObjectPtr graphicsObjectAt(int i) const override; private: - MeasureMinDistance m_dist; + MeasureDistance m_dist; Handle_AIS_Line m_gfxLength; Handle_AIS_TextLabel m_gfxDistText; Handle_AIS_Point m_gfxPnt1; @@ -195,16 +196,17 @@ class MeasureDisplayAngle : public BaseMeasureDisplay { class MeasureDisplayLength : public BaseMeasureDisplay { public: - MeasureDisplayLength(QuantityLength length); + MeasureDisplayLength(const MeasureLength& length); void update(const MeasureDisplayConfig& config) override; - int graphicsObjectsCount() const override { return 0; } - GraphicsObjectPtr graphicsObjectAt(int /*i*/) const override { return {}; } + int graphicsObjectsCount() const override { return 1; } + GraphicsObjectPtr graphicsObjectAt(int i) const override; bool isSumSupported() const override { return true; } void sumAdd(const IMeasureDisplay& other) override; private: - QuantityLength m_length; + MeasureLength m_length; + Handle_AIS_TextLabel m_gfxLenText; }; // -- @@ -213,16 +215,17 @@ class MeasureDisplayLength : public BaseMeasureDisplay { class MeasureDisplayArea : public BaseMeasureDisplay { public: - MeasureDisplayArea(QuantityArea area); + MeasureDisplayArea(const MeasureArea& area); void update(const MeasureDisplayConfig& config) override; - int graphicsObjectsCount() const override { return 0; } - GraphicsObjectPtr graphicsObjectAt(int /*i*/) const override { return {}; } + int graphicsObjectsCount() const override { return 1; } + GraphicsObjectPtr graphicsObjectAt(int i) const override; bool isSumSupported() const override { return true; } void sumAdd(const IMeasureDisplay& other) override; private: - QuantityArea m_area; + MeasureArea m_area; + Handle_AIS_TextLabel m_gfxAreaText; }; } // namespace Mayo diff --git a/src/measure/measure_tool.cpp b/src/measure/measure_tool.cpp index 79d56c5e..2c688a72 100644 --- a/src/measure/measure_tool.cpp +++ b/src/measure/measure_tool.cpp @@ -37,6 +37,8 @@ MeasureValue IMeasureTool_computeValue( switch (type) { case MeasureType::MinDistance: return tool.minDistance(owner1, owner2); + case MeasureType::CenterDistance: + return tool.centerDistance(owner1, owner2); case MeasureType::Angle: return tool.angle(owner1, owner2); default: diff --git a/src/measure/measure_tool.h b/src/measure/measure_tool.h index 428d8e38..2b56d6b5 100644 --- a/src/measure/measure_tool.h +++ b/src/measure/measure_tool.h @@ -21,17 +21,25 @@ namespace Mayo { +enum class DistanceType { + None, + Mininmum, + CenterToCenter +}; + // Void measure value struct MeasureNone {}; -// Measure of minimum distance between two entities -struct MeasureMinDistance { - // Point on 1st entity where minimum distance is located +// Measure of a distance between two entities +struct MeasureDistance { + // Point on 1st entity from which the distance is measured gp_Pnt pnt1; - // Point on 2nd entity where minimum distance is located + // Point on 2nd entity from which the distance is measured gp_Pnt pnt2; - // Length of the minimum distance + // Length of the distance QuantityLength value; + // Distance type + DistanceType type = DistanceType::None; }; // Measure of a circle entity @@ -56,6 +64,24 @@ struct MeasureAngle { QuantityAngle value; }; +// Measure of the length of an entity +struct MeasureLength { + // Point being at the middle of the entity + gp_Pnt middlePnt; + // Value of the length + QuantityLength value; +}; + +// Measure of the area of an entity +struct MeasureArea { + // Point being at the middle of the entity + gp_Pnt middlePnt; + // Value of the area + QuantityArea value; +}; + + + // Provides an interface to various measurement services // Input data of a measure service is one or many graphics entities pointed to by GraphicsOwner objects class IMeasureTool { @@ -68,10 +94,11 @@ class IMeasureTool { virtual gp_Pnt vertexPosition(const GraphicsOwnerPtr& owner) const = 0; virtual MeasureCircle circle(const GraphicsOwnerPtr& owner) const = 0; - virtual MeasureMinDistance minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const = 0; + virtual MeasureDistance minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const = 0; + virtual MeasureDistance centerDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const = 0; virtual MeasureAngle angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const = 0; - virtual QuantityLength length(const GraphicsOwnerPtr& owner) const = 0; - virtual QuantityArea area(const GraphicsOwnerPtr& owner) const = 0; + virtual MeasureLength length(const GraphicsOwnerPtr& owner) const = 0; + virtual MeasureArea area(const GraphicsOwnerPtr& owner) const = 0; }; // Base interface for errors reported by measurement services of IMeasureTool @@ -85,10 +112,10 @@ using MeasureValue = std::variant< MeasureNone, // WARNING: ensure this is the first value type in the variant gp_Pnt, MeasureCircle, - MeasureMinDistance, + MeasureDistance, MeasureAngle, - QuantityLength, - QuantityArea + MeasureLength, + MeasureArea >; bool MeasureValue_isValid(const MeasureValue& res); diff --git a/src/measure/measure_tool_brep.cpp b/src/measure/measure_tool_brep.cpp index 608649f1..f4132fb7 100644 --- a/src/measure/measure_tool_brep.cpp +++ b/src/measure/measure_tool_brep.cpp @@ -6,8 +6,10 @@ #include "measure_tool_brep.h" +#include "../base/brep_utils.h" #include "../base/geom_utils.h" #include "../base/math_utils.h" +#include "../base/mesh_utils.h" #include "../base/text_id.h" #include "../graphics/graphics_shape_object_driver.h" @@ -15,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -48,7 +51,9 @@ enum class ErrorCode { NotCircularEdge, NotBRepShape, NotGeometricOrPolygonEdge, + NotGeometricOrTriangulationFace, MinDistanceFailure, + CenterFailure, NotAllEdges, NotLinearEdge, NotAllFaces, @@ -70,8 +75,12 @@ class BRepMeasureError : public IMeasureError { return textIdTr("Entity must be a shape(BREP)"); case ErrorCode::NotGeometricOrPolygonEdge: return textIdTr("Entity must be a geometric or polygon edge"); + case ErrorCode::NotGeometricOrTriangulationFace: + return textIdTr("Entity must be a geometric or triangulation face"); case ErrorCode::MinDistanceFailure: return textIdTr("Computation of minimum distance failed"); + case ErrorCode::CenterFailure: + return textIdTr("Unable to find center of the shape"); case ErrorCode::NotAllEdges: return textIdTr("All entities must be edges"); case ErrorCode::NotLinearEdge: @@ -96,9 +105,53 @@ const TopoDS_Shape getShape(const GraphicsOwnerPtr& owner) { static const TopoDS_Shape nullShape; auto brepOwner = Handle_StdSelect_BRepOwner::DownCast(owner); - return brepOwner ? brepOwner->Shape().Moved(owner->Location()) : nullShape; + TopLoc_Location ownerLoc = owner->Location(); +#if OCC_VERSION_HEX >= 0x070600 + // Force scale factor to 1 + // If scale factor <> 1 then it will cause a crash(exception) in TopoDS_Shape::Move() starting + // from OpenCascade >= 7.6 + const double absScale = std::abs(ownerLoc.Transformation().ScaleFactor()); + const double scalePrec = TopLoc_Location::ScalePrec(); + if (absScale < (1. - scalePrec) || absScale > (1. + scalePrec)) { + gp_Trsf trsf = ownerLoc.Transformation(); + trsf.SetScaleFactor(1.); + ownerLoc = trsf; + } +#endif + return brepOwner ? brepOwner->Shape().Moved(ownerLoc) : nullShape; } +gp_Pnt computeShapeCenter(const TopoDS_Shape& shape) +{ + const TopAbs_ShapeEnum shapeType = shape.ShapeType(); + + if (shapeType == TopAbs_VERTEX) + return BRep_Tool::Pnt(TopoDS::Vertex(shape)); + + GProp_GProps shapeProps; + if (shapeType == TopAbs_FACE) { + // TODO Consider case where the face is cylindrical + BRepGProp::SurfaceProperties(shape, shapeProps); + } + else if (shapeType == TopAbs_WIRE) { + BRepGProp::LinearProperties(shape, shapeProps); + } + else if (shapeType == TopAbs_EDGE) { + try { + const MeasureCircle circle = MeasureToolBRep::brepCircle(shape); + return circle.value.Location(); + } + catch (const BRepMeasureError&) { + BRepGProp::LinearProperties(shape, shapeProps); + } + } + else { + throw BRepMeasureError(); + } + + throwErrorIf(shapeProps.Mass() < Precision::Confusion()); + return shapeProps.CentreOfMass(); +} } // namespace Span MeasureToolBRep::selectionModes(MeasureType type) const @@ -115,7 +168,8 @@ Span MeasureToolBRep::selectionModes(MeasureT static const GraphicsObjectSelectionMode modes[] = { AIS_Shape::SelectionMode(TopAbs_EDGE) }; return modes; } - case MeasureType::MinDistance: { + case MeasureType::MinDistance: + case MeasureType::CenterDistance: { static const GraphicsObjectSelectionMode modes[] = { AIS_Shape::SelectionMode(TopAbs_VERTEX), AIS_Shape::SelectionMode(TopAbs_EDGE), @@ -154,29 +208,29 @@ MeasureCircle MeasureToolBRep::circle(const GraphicsOwnerPtr& owner) const return brepCircle(getShape(owner)); } -MeasureMinDistance MeasureToolBRep::minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +MeasureDistance MeasureToolBRep::minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const { return brepMinDistance(getShape(owner1), getShape(owner2)); } +MeasureDistance MeasureToolBRep::centerDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +{ + return brepCenterDistance(getShape(owner1), getShape(owner2)); +} + MeasureAngle MeasureToolBRep::angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const { return brepAngle(getShape(owner1), getShape(owner2)); } -QuantityLength MeasureToolBRep::length(const GraphicsOwnerPtr& owner) const +MeasureLength MeasureToolBRep::length(const GraphicsOwnerPtr& owner) const { return brepLength(getShape(owner)); } -QuantityArea MeasureToolBRep::area(const GraphicsOwnerPtr& owner) const +MeasureArea MeasureToolBRep::area(const GraphicsOwnerPtr& owner) const { - const TopoDS_Shape shape = getShape(owner); - throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_FACE); - GProp_GProps gprops; - BRepGProp::SurfaceProperties(TopoDS::Face(shape), gprops); - const double area = gprops.Mass(); - return area * Quantity_SquareMillimeter; + return brepArea(getShape(owner)); } gp_Pnt MeasureToolBRep::brepVertexPosition(const TopoDS_Shape& shape) @@ -265,13 +319,13 @@ MeasureCircle MeasureToolBRep::brepCircle(const TopoDS_Shape& shape) { throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_EDGE); const TopoDS_Edge& edge = TopoDS::Edge(shape); - if (BRep_Tool::IsGeometric(edge)) + if (BRepUtils::isGeometric(edge)) return MeasureToolBRep::brepCircleFromGeometricEdge(edge); else return MeasureToolBRep::brepCircleFromPolygonEdge(edge); } -MeasureMinDistance MeasureToolBRep::brepMinDistance( +MeasureDistance MeasureToolBRep::brepMinDistance( const TopoDS_Shape& shape1, const TopoDS_Shape& shape2) { throwErrorIf(shape1.IsNull()); @@ -280,10 +334,28 @@ MeasureMinDistance MeasureToolBRep::brepMinDistance( const BRepExtrema_DistShapeShape dist(shape1, shape2); throwErrorIf(!dist.IsDone()); - MeasureMinDistance distResult; + MeasureDistance distResult; distResult.pnt1 = dist.PointOnShape1(1); distResult.pnt2 = dist.PointOnShape2(1); distResult.value = dist.Value() * Quantity_Millimeter; + distResult.type = DistanceType::Mininmum; + return distResult; +} + +MeasureDistance MeasureToolBRep::brepCenterDistance( + const TopoDS_Shape& shape1, const TopoDS_Shape& shape2) +{ + throwErrorIf(shape1.IsNull()); + throwErrorIf(shape2.IsNull()); + + const gp_Pnt centerOfMass1 = computeShapeCenter(shape1); + const gp_Pnt centerOfMass2 = computeShapeCenter(shape2); + + MeasureDistance distResult; + distResult.pnt1 = centerOfMass1; + distResult.pnt2 = centerOfMass2; + distResult.value = centerOfMass1.Distance(centerOfMass2) * Quantity_Millimeter; + distResult.type = DistanceType::CenterToCenter; return distResult; } @@ -296,6 +368,7 @@ MeasureAngle MeasureToolBRep::brepAngle(const TopoDS_Shape& shape1, const TopoDS TopoDS_Edge edge1 = TopoDS::Edge(shape1); TopoDS_Edge edge2 = TopoDS::Edge(shape2); + // TODO What if edge1 and edge2 are not geometric? const BRepAdaptor_Curve curve1(edge1); const BRepAdaptor_Curve curve2(edge2); @@ -351,28 +424,89 @@ MeasureAngle MeasureToolBRep::brepAngle(const TopoDS_Shape& shape1, const TopoDS return angleResult; } -QuantityLength MeasureToolBRep::brepLength(const TopoDS_Shape& shape) +MeasureLength MeasureToolBRep::brepLength(const TopoDS_Shape& shape) { + MeasureLength lenResult; + throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_EDGE); const TopoDS_Edge& edge = TopoDS::Edge(shape); - if (BRep_Tool::IsGeometric(edge)) { - const BRepAdaptor_Curve curve(TopoDS::Edge(shape)); + if (BRepUtils::isGeometric(edge)) { + const BRepAdaptor_Curve curve(edge); const double len = GCPnts_AbscissaPoint::Length(curve, 1e-6); - return len * Quantity_Millimeter; + lenResult.value = len * Quantity_Millimeter; + const GCPnts_QuasiUniformAbscissa pnts(curve, 3); + if (pnts.IsDone() && pnts.NbPoints() == 3) { + lenResult.middlePnt = GeomUtils::d0(curve, pnts.Parameter(2)); + } + else { + const double midParam = (curve.FirstParameter() + curve.LastParameter()) / 2.; + lenResult.middlePnt = GeomUtils::d0(curve, midParam); + } } else { TopLoc_Location loc; const Handle(Poly_Polygon3D)& polyline = BRep_Tool::Polygon3D(edge, loc); throwErrorIf(polyline.IsNull()); double len = 0.; + // Compute length of the polygon for (int i = 2; i <= polyline->NbNodes(); ++i) { const gp_Pnt& pnt1 = polyline->Nodes().Value(i - 1); const gp_Pnt& pnt2 = polyline->Nodes().Value(i); len += pnt1.Distance(pnt2); } - return len * Quantity_Millimeter; + lenResult.value = len * Quantity_Millimeter; + + // Compute middle point of the polygon + double accumLen = 0.; + for (int i = 2; i <= polyline->NbNodes() && accumLen < (len / 2.); ++i) { + const gp_Pnt& pnt1 = polyline->Nodes().Value(i - 1); + const gp_Pnt& pnt2 = polyline->Nodes().Value(i); + accumLen += pnt1.Distance(pnt2); + if (accumLen > (len / 2.)) { + const gp_Pnt pntLoc1 = pnt1.Transformed(loc); + const gp_Pnt pntLoc2 = pnt2.Transformed(loc); + lenResult.middlePnt = pntLoc1.Translated(gp_Vec{pntLoc1, pntLoc2} / 2.); + } + } } + + return lenResult; +} + +MeasureArea MeasureToolBRep::brepArea(const TopoDS_Shape& shape) +{ + MeasureArea areaResult; + throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_FACE); + const TopoDS_Face& face = TopoDS::Face(shape); + + if (BRepUtils::isGeometric(face)) { + GProp_GProps gprops; + BRepGProp::SurfaceProperties(face, gprops); + const double area = gprops.Mass(); + areaResult.value = area * Quantity_SquareMillimeter; + + const BRepAdaptor_Surface surface(face); + areaResult.middlePnt = surface.Value( + (surface.FirstUParameter() + surface.LastUParameter()) / 2., + (surface.FirstVParameter() + surface.LastVParameter()) / 2. + ); + } + else { + TopLoc_Location loc; + const Handle_Poly_Triangulation& triangulation = BRep_Tool::Triangulation(face, loc); + throwErrorIf(triangulation.IsNull()); + areaResult.value = MeshUtils::triangulationArea(triangulation) * Quantity_SquareMillimeter; + + for (int i = 1; i <= triangulation->NbNodes(); ++i) { + const gp_Pnt node = triangulation->Node(i).Transformed(loc); + areaResult.middlePnt.Translate(node.XYZ()); + } + + areaResult.middlePnt.ChangeCoord().Divide(triangulation->NbNodes()); + } + + return areaResult; } } // namespace Mayo diff --git a/src/measure/measure_tool_brep.h b/src/measure/measure_tool_brep.h index dec02fdd..777fb76b 100644 --- a/src/measure/measure_tool_brep.h +++ b/src/measure/measure_tool_brep.h @@ -22,16 +22,19 @@ class MeasureToolBRep : public IMeasureTool { gp_Pnt vertexPosition(const GraphicsOwnerPtr& owner) const override; MeasureCircle circle(const GraphicsOwnerPtr& owner) const override; - MeasureMinDistance minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const override; + MeasureDistance minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const override; + MeasureDistance centerDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const override; MeasureAngle angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const override; - QuantityLength length(const GraphicsOwnerPtr& owner) const override; - QuantityArea area(const GraphicsOwnerPtr& owner) const override; + MeasureLength length(const GraphicsOwnerPtr& owner) const override; + MeasureArea area(const GraphicsOwnerPtr& owner) const override; static gp_Pnt brepVertexPosition(const TopoDS_Shape& shape); static MeasureCircle brepCircle(const TopoDS_Shape& shape); - static MeasureMinDistance brepMinDistance(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2); + static MeasureDistance brepMinDistance(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2); + static MeasureDistance brepCenterDistance(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2); static MeasureAngle brepAngle(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2); - static QuantityLength brepLength(const TopoDS_Shape& shape); + static MeasureLength brepLength(const TopoDS_Shape& shape); + static MeasureArea brepArea(const TopoDS_Shape& shape); private: static MeasureCircle brepCircleFromGeometricEdge(const TopoDS_Edge& edge); diff --git a/src/measure/measure_type.h b/src/measure/measure_type.h index 0684cfc9..b1bb640d 100644 --- a/src/measure/measure_type.h +++ b/src/measure/measure_type.h @@ -9,7 +9,7 @@ namespace Mayo { enum class MeasureType { - None, VertexPosition, CircleCenter, CircleDiameter, MinDistance, Angle, Length, Area + None, VertexPosition, CircleCenter, CircleDiameter, MinDistance, CenterDistance, Angle, Length, Area }; } // namespace Mayo diff --git a/tests/inputs/face_trsf_scale_almost_1.stl b/tests/inputs/face_trsf_scale_almost_1.stl new file mode 100644 index 00000000..2eb24f97 Binary files /dev/null and b/tests/inputs/face_trsf_scale_almost_1.stl differ diff --git a/tests/runtests.cpp b/tests/runtests.cpp index c315fe41..78739dd3 100644 --- a/tests/runtests.cpp +++ b/tests/runtests.cpp @@ -8,28 +8,112 @@ #include "test_measure.h" #include "test_app.h" +#include + #include #include #include namespace Mayo { +namespace { + +// Helper function to return the path of some temporary file +// The file path should be unique just after function call +QString getTemporaryFilePath() +{ + QTemporaryFile file; + if (file.open()) + return file.fileName(); + + return {}; +} + +// Helper function to check equality between two C strings +bool cstringEqual(const char* lhs, const char* rhs) +{ + return std::strcmp(lhs, rhs) == 0; +} + +// Helper struct to hold the output filepath and format specified in command line +struct OutputFile { + QString path; + QString format; +}; + +// Retrieve the filename and format from string 'optionCmdLine' +// The 'optionCmdLine' argument should be the option specified in command line just after '-o' +// For example: +// $> mayo --runtests -o filename,format +// $> mayo --runtests -o filename +OutputFile parseOutputFile(const QString& optionCmdLine) +{ + OutputFile result; + const int posComma = optionCmdLine.lastIndexOf(','); + if (posComma != -1) { + result.path = optionCmdLine.left(posComma); + result.format = optionCmdLine.right(optionCmdLine.size() - posComma); + } + else { + result.path = optionCmdLine; + } + + return result; +} + +} // namespace + int runTests(int argc, char* argv[]) { + // Preprocess command-line arguments QStringList args; + QString* ptrArgOutputFileName = nullptr; for (int i = 0; i < argc; ++i) { - if (std::strcmp(argv[i], "--runtests") != 0) + // Don't keep "--runtests" argument because QText::qExec() will reject it + if (!cstringEqual(argv[i], "--runtests")) args.push_back(QString::fromUtf8(argv[i])); + + // Keep track of the output filename argument(specified after "-o" option) + if (i > 0 && cstringEqual(argv[i-1], "-o") && !args.empty() && !args.back().startsWith('-')) + ptrArgOutputFileName = &args.back(); } - int retcode = 0; + // Retrieve the output filename and format(separated by comma, eg "filename,format") + const QString argOutputFileName = ptrArgOutputFileName ? *ptrArgOutputFileName : QString{}; + const OutputFile argOutputFile = parseOutputFile(argOutputFileName); + + // Declare unit tests to be checked std::vector> vecTest; vecTest.emplace_back(new Mayo::TestBase); vecTest.emplace_back(new Mayo::TestMeasure); vecTest.emplace_back(new Mayo::TestApp); - for (const std::unique_ptr& test : vecTest) + + // Execute unit tests + // As QText::qExec() is called for each test object, it would overwrite any output file + // specified with "-o filename,format" + // This is solved by substituing output file argument with a temporary file whose contents + // is appended to the target output file + int retcode = 0; + for (const std::unique_ptr& test : vecTest) { + // Replace the output file argument with a temporary file path + const QString outputTestFileName = ptrArgOutputFileName ? getTemporaryFilePath() : QString{}; + if (ptrArgOutputFileName) + *ptrArgOutputFileName = outputTestFileName + argOutputFile.format; + + // Execute test retcode += QTest::qExec(test.get(), args); + // Append the temporary file to the target output file + if (ptrArgOutputFileName) { + const bool isFirstTest = &test == &vecTest.front(); + QFile outputTestFile(outputTestFileName); + QFile outputFile(argOutputFile.path); + outputTestFile.open(QIODevice::ReadOnly); + outputFile.open(QIODevice::WriteOnly | (isFirstTest ? QIODevice::NotOpen : QIODevice::Append)); + outputFile.write(outputTestFile.readAll()); + } + } + return retcode; } diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 7530422b..e98f482f 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -45,6 +45,7 @@ #include #include #include +#include #include #include @@ -78,6 +79,35 @@ Q_DECLARE_METATYPE(Mayo::PropertyValueConversion::Variant) namespace Mayo { +static std::optional findFrLocale() +{ + auto fnGetLocale = [](const char* name) -> std::optional { + try { + return std::locale(name); + } catch (...) { + qWarning().noquote() << QString("Locale '%1' not available").arg(name); + } + + return {}; + }; + + // Tests with "fr_FR" locale which is likely to be Windows-1252 or ISO8859-1 on Unix + std::vector frLocaleNames = { "fr_FR.ISO8859-15", "fr_FR.ISO-8859-15" }; +#ifndef MAYO_OS_WINDOWS + // No native utf8 support on Windows(or requires Windows 10 november 2019 update) + frLocaleNames.push_back("fr_FR.utf8"); +#endif + frLocaleNames.push_back("fr_FR"); + + std::optional frLocale; + for (const char* localeName : frLocaleNames) { + if (!frLocale) + frLocale = fnGetLocale(localeName); + } + + return frLocale; +} + // For the sake of QCOMPARE() static bool operator==(const UnitSystem::TranslateResult& lhs, const UnitSystem::TranslateResult& rhs) { @@ -256,6 +286,36 @@ void TestBase::FilePath_test() } } +void TestBase::PropertyValueConversionVariant_doubleToInt_test() +{ + using Variant = PropertyValueConversion::Variant; + QFETCH(double, doubleValue); + QFETCH(bool, ok); + + const Variant dvar(doubleValue); + bool okActual = false; + const int asIntValue = dvar.toInt(&okActual); + if (ok) { + QCOMPARE(asIntValue, std::floor(doubleValue)); + } else { + QCOMPARE(asIntValue, 0); + } + + QCOMPARE(okActual, ok); +} + +void TestBase::PropertyValueConversionVariant_doubleToInt_test_data() +{ + QTest::addColumn("doubleValue"); + QTest::addColumn("ok"); + QTest::newRow("50.25") << 50.25 << true; + QTest::newRow("-50.25") << -50.25 << true; + QTest::newRow("INT_MAX+1") << (double(INT_MAX) + 1.) << false; + QTest::newRow("INT_MIN-1") << (double(INT_MIN) - 1.) << false; + QTest::newRow("INT_MAX") << double(INT_MAX) << true; + QTest::newRow("INT_MIN") << double(INT_MIN) << true; +} + void TestBase::PropertyValueConversion_test() { QFETCH(QString, strPropertyName); @@ -281,6 +341,9 @@ void TestBase::PropertyValueConversion_test() enum class MayoTest_Color { Bleu, Blanc, Rouge }; prop.reset(new PropertyEnum(nullptr, {})); } + else if (strPropertyName == PropertyFilePath::TypeName) { + prop.reset(new PropertyFilePath(nullptr, {})); + } QVERIFY(prop); @@ -307,6 +370,18 @@ void TestBase::PropertyValueConversion_test_data() QTest::newRow("Enumeration(Color)") << PropertyEnumeration::TypeName << Variant("Blanc"); } +void TestBase::PropertyValueConversion_bugGitHub219_test() +{ + const std::string strPath = "c:\\é_à_À_œ_ç"; + PropertyValueConversion conv; + PropertyFilePath propFilePath(nullptr, {}); + const bool ok = conv.fromVariant(&propFilePath, strPath); + QVERIFY(ok); + //qDebug() << "strPath:" << QByteArray::fromStdString(strPath); + //qDebug() << "propFilePath:" << QByteArray::fromStdString(propFilePath.value().u8string()); + QCOMPARE(propFilePath.value().u8string(), strPath); +} + void TestBase::PropertyQuantityValueConversion_test() { QFETCH(QString, strPropertyName); @@ -524,30 +599,7 @@ void TestBase::IO_bugGitHub166_test_data() void TestBase::DoubleToString_test() { - auto fnGetLocale = [](const char* name) -> std::optional { - try { - return std::locale(name); - } catch (...) { - qWarning().noquote() << QString("Locale '%1' not available").arg(name); - } - - return {}; - }; - - // Tests with "fr_FR" locale which is likely to be Windows-1252 or ISO8859-1 on Unix - std::vector frLocaleNames = { "fr_FR.ISO8859-15", "fr_FR.ISO-8859-15" }; -#ifndef MAYO_OS_WINDOWS - // No native utf8 support on Windows(or requires Windows 10 november 2019 update) - frLocaleNames.push_back("fr_FR.utf8"); -#endif - frLocaleNames.push_back("fr_FR"); - - std::optional frLocale; - for (const char* localeName : frLocaleNames) { - if (!frLocale) - frLocale = fnGetLocale(localeName); - } - + std::optional frLocale = findFrLocale(); if (frLocale) { qInfo() << "frLocale:" << QString::fromStdString(frLocale->name()); // 1258. @@ -579,6 +631,16 @@ void TestBase::DoubleToString_test() QCOMPARE(to_stdString(-45.6789).locale(cLocale).decimalCount(6).get(), "-45.6789"); } +void TestBase::StringConv_test() +{ + std::optional frLocale = findFrLocale(); + if (frLocale) { + const std::string stdStr = to_stdString(14758.5).locale(frLocale.value()); + const auto occExtStr = to_OccExtString(stdStr); + QCOMPARE(stdStr, to_stdString(occExtStr)); + } +} + void TestBase::BRepUtils_test() { QVERIFY(BRepUtils::moreComplex(TopAbs_COMPOUND, TopAbs_SOLID)); @@ -687,7 +749,7 @@ void TestBase::Enumeration_test() Enumeration baseEnum = Enumeration::fromType(); QVERIFY(!baseEnum.empty()); QCOMPARE(baseEnum.size(), MetaEnum::count()); - QCOMPARE(baseEnum.items().size(), baseEnum.size()); + QCOMPARE(baseEnum.items().size(), static_cast(baseEnum.size())); for (const auto& enumEntry : MetaEnum::entries()) { QVERIFY(baseEnum.contains(enumEntry.second)); QCOMPARE(baseEnum.findValueByName(enumEntry.second), int(enumEntry.first)); @@ -767,8 +829,11 @@ void TestBase::MeshUtils_test() for (int i = 1; i <= polyTri->NbTriangles(); ++i) { int n1, n2, n3; polyTri->Triangle(i).Get(n1, n2, n3); - polyTriBox->ChangeTriangle(idTriangleOffset + i).Set( - idNodeOffset + n1, idNodeOffset + n2, idNodeOffset + n3); + MeshUtils::setTriangle( + polyTriBox, + idTriangleOffset + i, + { idNodeOffset + n1, idNodeOffset + n2, idNodeOffset + n3 } + ); } idNodeOffset += polyTri->NbNodes(); @@ -899,9 +964,9 @@ void TestBase::UnitSystem_test_data() QTest::newRow("time(5s)") << UnitSystem::milliseconds(5 * Quantity_Second) << UnitSystem::TranslateResult{ 5000., "ms", Quantity_Millisecond.value() }; - QTest::newRow("time(5s)") - << UnitSystem::milliseconds(5 * Quantity_Second) - << UnitSystem::TranslateResult{ 5000., "ms", Quantity_Millisecond.value() }; + QTest::newRow("time(2min)") + << UnitSystem::milliseconds(2 * Quantity_Minute) + << UnitSystem::TranslateResult{ 2 * 60 * 1000., "ms", Quantity_Millisecond.value() }; } void TestBase::LibTask_test() @@ -1004,6 +1069,21 @@ void TestBase::LibTree_test() } } +void TestBase::Span_test() +{ + const std::vector vecString = { "first", "second", "third", "fourth", "fifth" }; + const std::string& item0 = vecString.at(0); + const std::string& item1 = vecString.at(1); + const std::string& item2 = vecString.at(2); + const std::string& item3 = vecString.at(3); + const std::string& item4 = vecString.at(4); + QCOMPARE(Span_itemIndex(vecString, item0), 0); + QCOMPARE(Span_itemIndex(vecString, item1), 1); + QCOMPARE(Span_itemIndex(vecString, item2), 2); + QCOMPARE(Span_itemIndex(vecString, item3), 3); + QCOMPARE(Span_itemIndex(vecString, item4), 4); +} + void TestBase::initTestCase() { m_ioSystem = new IO::System; diff --git a/tests/test_base.h b/tests/test_base.h index 1d188b2e..b5a73817 100644 --- a/tests/test_base.h +++ b/tests/test_base.h @@ -26,8 +26,13 @@ private slots: void FilePath_test(); + void PropertyValueConversionVariant_doubleToInt_test(); + void PropertyValueConversionVariant_doubleToInt_test_data(); + void PropertyValueConversion_test(); void PropertyValueConversion_test_data(); + void PropertyValueConversion_bugGitHub219_test(); + void PropertyQuantityValueConversion_test(); void PropertyQuantityValueConversion_test_data(); @@ -40,6 +45,7 @@ private slots: void IO_bugGitHub166_test_data(); void DoubleToString_test(); + void StringConv_test(); void BRepUtils_test(); @@ -66,6 +72,8 @@ private slots: void LibTask_test(); void LibTree_test(); + void Span_test(); + void initTestCase(); void cleanupTestCase(); diff --git a/tests/test_measure.cpp b/tests/test_measure.cpp index ce835070..5c2a6606 100644 --- a/tests/test_measure.cpp +++ b/tests/test_measure.cpp @@ -6,8 +6,11 @@ #include "test_measure.h" +#include "../src/base/application.h" #include "../src/base/geom_utils.h" +#include "../src/base/task_progress.h" #include "../src/base/unit_system.h" +#include "../src/io_occ/io_occ_stl.h" #include "../src/measure/measure_tool_brep.h" #include @@ -123,7 +126,7 @@ void TestMeasure::BRepMinDistance_TwoPoints_test() const gp_Pnt pnt2{ -57.4, 4487.56, 1.8 }; const TopoDS_Shape shape1 = BRepBuilderAPI_MakeVertex(pnt1); const TopoDS_Shape shape2 = BRepBuilderAPI_MakeVertex(pnt2); - const MeasureMinDistance minDist = MeasureToolBRep::brepMinDistance(shape1, shape2); + const MeasureDistance minDist = MeasureToolBRep::brepMinDistance(shape1, shape2); QVERIFY(minDist.pnt1.IsEqual(pnt1, Precision::Confusion())); QVERIFY(minDist.pnt2.IsEqual(pnt2, Precision::Confusion())); QCOMPARE(UnitSystem::millimeters(minDist.value).value, pnt1.Distance(pnt2)); @@ -137,7 +140,7 @@ void TestMeasure::BRepMinDistance_TwoBoxes_test() const gp_Pnt box2_max{ 55, 7, 7 }; const TopoDS_Shape shape1 = BRepPrimAPI_MakeBox(box1_min, box1_max); const TopoDS_Shape shape2 = BRepPrimAPI_MakeBox(box2_min, box2_max); - const MeasureMinDistance minDist = MeasureToolBRep::brepMinDistance(shape1, shape2); + const MeasureDistance minDist = MeasureToolBRep::brepMinDistance(shape1, shape2); QCOMPARE(UnitSystem::millimeters(minDist.value).value, std::abs(box1_max.X() - box2_min.X())); QCOMPARE(UnitSystem::millimeters(minDist.value).value, minDist.pnt1.Distance(minDist.pnt2)); } @@ -167,8 +170,23 @@ void TestMeasure::BRepLength_PolygonEdge_test() points.ChangeValue(4) = gp_Pnt{7, 10, 5}; points.ChangeValue(5) = gp_Pnt{7, 12, 5}; const TopoDS_Edge edge = makePolygonEdge(points); - const QuantityLength len = MeasureToolBRep::brepLength(edge); - QCOMPARE(UnitSystem::millimeters(len).value, 24.); + const MeasureLength len = MeasureToolBRep::brepLength(edge); + QCOMPARE(UnitSystem::millimeters(len.value).value, 24.); +} + +void TestMeasure::BRepArea_TriangulationFace() +{ + auto progress = &TaskProgress::null(); + IO::OccStlReader reader; + const bool okRead = reader.readFile("tests/inputs/face_trsf_scale_almost_1.stl", progress); + QVERIFY(okRead); + + auto doc = Application::instance()->newDocument(); + const TDF_LabelSequence seqLabel = reader.transfer(doc, progress); + QCOMPARE(seqLabel.Size(), 1); + const TopoDS_Shape shape = doc->xcaf().shape(seqLabel.First()); + const MeasureArea area = MeasureToolBRep::brepArea(shape); + QVERIFY(std::abs(double(UnitSystem::squareMillimeters(area.value)) - 597.6224) < 0.0001); } } // namespace Mayo diff --git a/tests/test_measure.h b/tests/test_measure.h index 8950ddd8..c66d9756 100644 --- a/tests/test_measure.h +++ b/tests/test_measure.h @@ -6,6 +6,9 @@ #pragma once +#if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES) +# define _USE_MATH_DEFINES // Fix M_E, M_PI, ... macro redefinitions with qmath.h +#endif #include #include @@ -28,6 +31,8 @@ private slots: void BRepAngle_TwoLinesParallelError_test(); void BRepLength_PolygonEdge_test(); + + void BRepArea_TriangulationFace(); }; } // namespace Mayo diff --git a/version.pri b/version.pri index 79555ca8..07bcd9d9 100644 --- a/version.pri +++ b/version.pri @@ -9,10 +9,10 @@ defined(HAVE_GIT, var) { } MAYO_VERSION_MAJ = 0 -MAYO_VERSION_MIN = 7 +MAYO_VERSION_MIN = 8 MAYO_VERSION_PAT = 0 -VERSION = $${MAYO_VERSION_MAJ}.$${MAYO_VERSION_MIN}.$${MAYO_VERSION_PAT}.$${MAYO_VERSION_REVNUM} -MAYO_VERSION = $${VERSION}-$$MAYO_VERSION_COMMIT +VERSION = $${MAYO_VERSION_MAJ}.$${MAYO_VERSION_MIN}.$${MAYO_VERSION_PAT} +MAYO_VERSION = $${VERSION} equals(QT_ARCH, i386) { VERSION_TARGET_ARCH = x86