diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..22a2411f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,86 @@ +name: CI + +on: [push] + +jobs: + ci: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest] + #os: [ubuntu-latest, macos-latest] + + runs-on: ${{matrix.os}} + + 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 + with: + cached: ${{ steps.cache-qt.outputs.cache-hit }} + + - name: Install OpenCascade[Ubuntu] + if: startsWith(matrix.os, 'ubuntu') + 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"` + GH_CASCADE_LIB_DIR=`dpkg -L libocct-foundation-dev | grep -i "libTKernel.so" | sed "s/\/libTKernel.so//i"` + 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') + run: | + brew install opencascade + GH_CASCADE_BASE_DIR=`brew --cellar opencascade` + GH_CASCADE_VERSION=`brew info opencascade | grep -E --only-matching --max-count=1 "[0-9]\.[0-9]\.[0-9]"` + echo "GH_CASCADE_INC_DIR=$GH_CASCADE_BASE_DIR/$GH_CASCADE_VERSION/include/opencascade" >> $GITHUB_ENV + echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_BASE_DIR/$GH_CASCADE_VERSION/lib" >> $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 + run: | + echo CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} + echo CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} + 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: | + make -j${{steps.cpu-cores.outputs.count}} + + - name: Execute Unit Tests[Ubuntu] + if: startsWith(matrix.os, 'ubuntu') + working-directory: ${{github.workspace}}/build + env: + DISPLAY: :0 + run: | + Xvfb $DISPLAY -screen 0 1280x1024x24 & + sleep 5s + ./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/.gitignore b/.gitignore index 8356b696..39ce3cf0 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ build-* *.user.* installer/setupvars.iss installer/Output +custom.pri \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a5f4e4e5..00000000 --- a/.travis.yml +++ /dev/null @@ -1,14 +0,0 @@ -language: cpp - -dist: focal - -before_install: - - sudo apt-get -y install qtbase5-dev libqt5svg5-dev - - sudo apt-get -y install libocct-data-exchange-dev libocct-draw-dev - -compiler: - - gcc - -script: - - chmod +x scripts/travis-build.sh - - ./scripts/travis-build.sh diff --git a/README.md b/README.md index 42db2a4f..db904fbe 100644 --- a/README.md +++ b/README.md @@ -1,60 +1,93 @@ -

- -

- +
+ +[![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?svg=true)](https://ci.appveyor.com/project/HuguesDelorme/mayo) -[![Build Status](https://img.shields.io/travis/fougue/mayo/develop.svg?logo=travis)](https://app.travis-ci.com/fougue/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) +[![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.6.0-blue.svg?style=flat)](https://github.com/fougue/mayo/releases) + +
-# What is Mayo -Mayo is an opensource 3D CAD viewer and converter - -# Overview -* View and convert 3D files in different formats -* Explore assembly trees(product structure) and view properties -* Cross platform: runs on Windows, Linux and macOS -* Underlying toolkits: OpenCascade and Qt - -# Current features -* 3D exploding of the model tree, allowing better exploration of complex designs -* 3D clip planes with configurable capping -* 3D view cube providing intuitive camera manipulation -* Save image(snapshot) of the current 3D view -* Quick access to the CAD files recently open thanks to thumbnails in the Home page -* Toggle visibility of any item from the Model tree(use checkbox) -* Customizable precision of the meshes computed from BRep shapes, affecting visualization quality and conversion into mesh formats -* Convert files to multiple CAD formats from command-line interface(CLI) - -3D viewer operations : -* Rotate : mouse left + move -* Pan : mouse right + move -* Zoom : mouse wheel(scroll) -* Window zoom : mouse wheel + move -* Instant zoom : space bar -* Select Object: mouse left click -* Select Objects: SHIFT + mouse left clicks - -# Supported formats - Formats | Import | Export | Notes ---------------------------|-----------|----------|------------------------------ -STEP | ✔ | ✔ | AP203, 214, 242(some parts) -IGES | ✔ | ✔ | v5.3 -OpenCascade BREP | ✔ | ✔ | -DXF | ✔ | ❌ | -OBJ | ✔ | ✔ | Import requires OpenCascade ≥ v7.4.0
Export requires OpenCascade ≥ v7.6.0 -glTF | ✔ | ✔ | Import requires OpenCascade ≥ v7.4.0
Export requires OpenCascade ≥ v7.5.0
Supports 1.0, 2.0 and GLB -VRML | ❌ | ✔ | v2.0 UTF8 -STL | ✔ | ✔ | ASCII/binary -AMF | ❌ | ✔ | v1.2 Text/ZIP
Requires [gmio](https://github.com/fougue/gmio) ≥ v0.4.0 - -Mayo provides precise control over [import](https://github.com/fougue/mayo/wiki/Import-parameters-by-CAD-format) and [export](https://github.com/fougue/mayo/wiki/Export-parameters-by-CAD-format) with many parameters per format. - -# Gallery - - +
+ Logo +

+

Mayo the opensource 3D CAD viewer and converter +

+ +
- +## :eyeglasses: Overview +- **Convert 3D files**
+Mayo can read/write 3D files from/to STEP, IGES, STL and many other [CAD formats](https://github.com/fougue/mayo/wiki/Supported-formats) + +- **Visualize 3D files**
+Mayo 3D viewer supports clip planes, exploding of assemblies, measurement of shapes, show/hide parts, ... + +- **Cross platform**
+Mayo runs on Windows, Linux and macOS + +- **Solid foundations**
+Mayo is developed in modern C++ with [Qt](https://www.qt.io) and [OpenCascade](https://dev.opencascade.org) + +## :zap: Features +- **3D clip planes** with configurable capping + +- **3D exploding of the model tree** allowing better exploration of complex designs + +- **3D measure tools** for circles, angles, lengths, areas, ... + +- **3D view cube** providing intuitive camera manipulation + +- **Quick access to CAD files** recently open thanks to thumbnails in the [Home page](https://github.com/fougue/mayo/blob/develop/doc/screenshot_5.png) + +- **Toggle item visibility** within the Model tree(use checkbox) + +- **Customizable mesh precision** for BREP shapes, affecting visualization quality and conversion into mesh formats +- **Convert files** to multiple CAD formats from [command-line interface](https://github.com/fougue/mayo/blob/develop/doc/screencast_cli.gif):computer: + +## :floppy_disk: Supported formats + Format | Import | Export | Notes +----------|--------------------|--------------------|------------------ +STEP | :white_check_mark: | :white_check_mark: | AP203, 214, 242 +IGES | :white_check_mark: | :white_check_mark: | v5.3 +BREP | :white_check_mark: | :white_check_mark: | OpenCascade format +DXF | :white_check_mark: | :x: | +OBJ | :white_check_mark: | :white_check_mark: | +glTF | :white_check_mark: | :white_check_mark: | 1.0, 2.0 and GLB +VRML | :x: | :white_check_mark: | v2.0 UTF8 +STL | :white_check_mark: | :white_check_mark: | ASCII/binary +AMF | :x: | :white_check_mark: | v1.2 Text/ZIP +PLY | :white_check_mark: | :white_check_mark: | ASCII/binary +Image | :x: | :white_check_mark: | PNG, JPEG, ... + +See also this dedicated [wikipage](https://github.com/fougue/mayo/wiki/Supported-formats) for more details + +## :mag: 3D viewer operations + + Operation | Mouse/Keyboard controls +---------------|-------------------------- +Rotate | mouseLeft + move +Pan | mouseRight + move +Zoom | mouseLeft + mouseRight + move +Zoom +/- | mouseWheel(scroll) +Window zoom | CTRL + mouseLeft + move +Instant zoom | spaceBar +Select Object | mouseLeft click +Select Objects | SHIFT + mouseLeft clicks + +Mayo supports also multiple 3D viewer navigation styles to mimic common CAD applications(CATIA, SOLIDWORKS, ...) + +## :hammer: How to build Mayo +[Instructions for Windows MSVC](https://github.com/fougue/mayo/wiki/Build-instructions-for-Windows-MSVC) +[Instructions for Debian](https://github.com/fougue/mayo/wiki/Build-instructions-for-Debian) +[Instructions for macOS](https://github.com/fougue/mayo/wiki/Build-instructions-for-macOS) + +## :clapper: Gallery + + + @@ -62,7 +95,3 @@ Mayo provides precise control over [import](https://github.com/fougue/mayo/wiki/ - -# How to build Mayo -[Instructions for Windows MSVC](https://github.com/fougue/mayo/wiki/Build-instructions-for-Windows-MSVC) -[Instructions for Debian](https://github.com/fougue/mayo/wiki/Build-instructions-for-Debian) diff --git a/appveyor.yml b/appveyor.yml index 19689da9..18fae026 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.5_build{build} +version: 0.6_build{build} image: Visual Studio 2017 platform: x64 @@ -36,36 +36,21 @@ before_build: - 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% - - set PATH="C:\Program Files (x86)\Inno Setup 5";%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 + - qmake ..\mayo.pro CONFIG+=withtests - jom -j%NUMBER_OF_PROCESSORS% - cd .. test_script: - - mkdir build-tests-%APPVEYOR_OCC_VERSION% - - cd build-tests-%APPVEYOR_OCC_VERSION% - - qmake ..\tests\mayo_tests.pro - - jom -j%NUMBER_OF_PROCESSORS% - - release\mayo_tests.exe + - cd build-%APPVEYOR_OCC_VERSION% + - release\mayo.exe --runtests - cd .. -after_test: - - if "%APPVEYOR_OCC_VERSION%"=="7.5.0" ( - cd build-%APPVEYOR_OCC_VERSION%\installer && - iscc setup.iss && - cd ..\.. - ) - -artifacts: - - path: build-%APPVEYOR_OCC_VERSION%\installer\Output\mayo_*_installer.exe - name: MayoInstallerWin64 - on_success: - ps: >- if ($true) diff --git a/doc/screencast_1.gif b/doc/screencast_1.gif index 9c070848..7ee71abd 100644 Binary files a/doc/screencast_1.gif and b/doc/screencast_1.gif differ diff --git a/i18n/i18n.pro b/i18n/i18n.pro index 4209997e..7adaae4b 100644 --- a/i18n/i18n.pro +++ b/i18n/i18n.pro @@ -4,6 +4,7 @@ HEADERS += \ $$files(../src/io_dxf/*.h) \ $$files(../src/io_gmio/*.h) \ $$files(../src/io_image/*.h) \ + $$files(../src/io_ply/*.h) \ $$files(../src/graphics/*.h) \ $$files(../src/gui/*.h) \ $$files(../src/app/*.h) \ @@ -15,6 +16,7 @@ SOURCES += \ $$files(../src/io_dxf/*.cpp) \ $$files(../src/io_gmio/*.cpp) \ $$files(../src/io_image/*.cpp) \ + $$files(../src/io_ply/*.cpp) \ $$files(../src/graphics/*.cpp) \ $$files(../src/gui/*.cpp) \ $$files(../src/app/*.cpp) \ diff --git a/i18n/mayo_en.qm b/i18n/mayo_en.qm index 6451f1c6..fb879dde 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 1d1c0574..25f56156 100644 --- a/i18n/mayo_en.ts +++ b/i18n/mayo_en.ts @@ -4,278 +4,483 @@ AppModule + VeryCoarse - Very Coarse + Very Coarse + Coarse - Coarse + Coarse + Normal - Normal + Normal + Precise - Precise + Precise + VeryPrecise - Very Precise + Very Precise + UserDefined - User Defined + User Defined Mayo::AppModule - + en English - + fr French - system - System + System - units - Units + Units - decimalCount - Count Of Decimals + Count Of Decimals - schema - Schema + Schema - application - Application + Application - language - Language + Language - recentFiles - Recent Files + Recent Files - lastOpenFolder - Last Open Folder + Last Open Folder - lastSelectedFormatFilter - Last Selected Format Filter + Last Selected Format Filter - linkWithDocumentSelector - Link With Document Selector + Link With Document Selector - graphics - Graphics + Graphics - defaultShowOriginTrihedron - Show Origin Trihedron By Default + Show Origin Trihedron By Default - clipPlanes - Clip planes + Clip planes - cappingOn - Capping + Capping - cappingHatchOn - Capping Hatch + Capping Hatch - meshDefaults - Mesh Defaults + Mesh Defaults - meshingQuality - Quality + Quality - meshingChordalDeflection - Chordal Deflection + Chordal Deflection - meshingAngularDeflection - Angular Deflection + Angular Deflection - meshingRelative - Relative + Relative - instantZoomFactor - Instant Zoom Factor + Instant Zoom Factor - color - Color + Color - edgeColor - Edge Color + Edge Color - material - Material + Material - showEgesOn - Show Edges + Show Edges - showNodesOn - Show Nodes + Show Nodes - + meshing + BRep Meshing + + + import + Import + + + export + Export + + + VeryCoarse + Very Coarse + + + Coarse + Coarse + + + Normal + Normal + + + Precise + Precise + + + VeryPrecise + Very Precise + + + UserDefined + User Defined + + + + Mayo::AppModuleProperties + + + language + Language + + + + system + System + + + + application + Application + + + meshing BRep Meshing - + + graphics + Graphics + + + + units + Units + + + + clipPlanes + Clip planes + + + + meshDefaults + Mesh Defaults + + + + import + Import + + + + export + 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 - - For the tesselation of faces the angular deflection limits the angle between subsequent segments in a polyline + + Controls precision of the mesh to be computed from the BRep shape - - Controls precision of the mesh to be computed from the BRep shape + + For the tesselation of faces the chordal deflection limits the distance between a curve and its tessellation - - For the tesselation of faces the chordal deflection limits the distance between a curve and its tessellation + + For the tesselation 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 + + + + 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 - - import - Import + + decimalCount + Count Of Decimals - - export - Export + + schema + Schema - - VeryCoarse - Very Coarse + + recentFiles + Recent Files - - Coarse - Coarse + + lastOpenFolder + Last Open Folder - - Normal - Normal + + lastSelectedFormatFilter + Last Selected Format Filter - - Precise - Precise + + linkWithDocumentSelector + Link With Document Selector - - VeryPrecise - Very Precise + + meshingQuality + Quality - - UserDefined - User Defined + + meshingChordalDeflection + Chordal Deflection + + + + meshingAngularDeflection + Angular Deflection + + + + meshingRelative + Relative + + + + navigationStyle + View Navigation Style + + + + defaultShowOriginTrihedron + Show Origin Trihedron By Default + + + + instantZoomFactor + Instant Zoom Factor + + + + cappingOn + Capping + + + + cappingHatchOn + Capping Hatch + + + + color + Color + + + + edgeColor + Edge Color + + + + material + Material + + + + showEgesOn + Show Edges + + + + showNodesOn + Show Nodes Mayo::Application - + Binary Mayo Document Format - + XML Mayo Document Format + + Mayo::BRepMeasureError + + + Entity must be a vertex + + + + + Entity must be a circular edge + + + + + Entity must be a shape(BREP) + + + + + Computation of minimum distance failed + + + + + All entities must be edges + + + + + Entity must be a linear edge + + + + + All entities must be faces + + + + + Entities must not be parallel + + + + + Unknown error + + + + + Mayo::CliExport + + + Mesh BRep shapes + + + + + Imported + + + + + Exported {} + + + + + Importing... + + + + + Exporting {}... + + + Mayo::DialogAbout @@ -563,64 +768,147 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho - Mayo::GraphicsMeshObjectDriver_ObjectProperties + Mayo::DocumentPropertyGroup - + + filepath + File Path + + + + fileSize + File Size + + + + createdDateTime + Created + + + + modifiedDateTime + Modified + + + + owner + Owner + + + + entityCount + Count Of Entities + + + + Mayo::GraphicsMeshObjectDriver + + + Mesh_Wireframe + [Mesh] Wireframe + + + + Mesh_Shaded + [Mesh] Shaded + + + + Mesh_Shrink + [Mesh] Shrink + + + color Color - + edgeColor Edge Color - + showEdges Show Edges - + showNodes Show Nodes + + Mayo::GraphicsMeshObjectDriver_ObjectProperties + + color + Color + + + edgeColor + Edge Color + + + showEdges + Show Edges + + + showNodes + Show Nodes + + Mayo::GraphicsObjectDriver - Shape_Wireframe - [Shape] Wireframe + [Shape] Wireframe - Shape_HiddenLineRemoval - [Shape] Hidden Line Removal + [Shape] Hidden Line Removal - Shape_Shaded - [Shape] Shaded + [Shape] Shaded - Shape_ShadedWithFaceBoundary - [Shape] Shaded With Edges + [Shape] Shaded With Edges - Mesh_Wireframe - [Mesh] Wireframe + [Mesh] Wireframe - Mesh_Shaded - [Mesh] Shaded + [Mesh] Shaded - Mesh_Shrink - [Mesh] Shrink + [Mesh] Shrink + + + + Mayo::GraphicsShapeObjectDriver + + + Shape_Wireframe + [Shape] Wireframe + + + + Shape_HiddenLineRemoval + [Shape] Hidden Line Removal + + + + Shape_Shaded + [Shape] Shaded + + + + Shape_ShadedWithFaceBoundary + [Shape] Shaded With Edges @@ -669,69 +957,69 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::IO::GmioAmfWriter::Properties - + Format used when writting `double` values as strings - + Decimal floating point(ex: 392.65) - + Scientific notation(ex: 3.9265E+2) - + Use the shortest representation: decimal or scientific - + Maximum number of significant digits when writting `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 - + float64Format 64bit Float Format - + float64Precision 64bit Float Precision - + createZipArchive Create ZIP Archive - + zipEntryFilename ZIP Entry Filename - + useZip64 Use ZIP64 extensions @@ -762,52 +1050,52 @@ Only applicable if option `{}` is on Mayo::IO::ImageWriterI18N - + 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 @@ -840,65 +1128,6 @@ Only applicable if option `{}` is on System length units to convert into while reading files - - Mayo::IO::OccCommon - - - - Undefined - - - - - posYfwd_posZup - - - - - negZfwd_posYup - - - - - Micrometer - - - - - Millimeter - - - - - Centimeter - - - - - Meter - - - - - Kilometer - - - - - Inch - - - - - Foot - - - - - Mile - - - Mayo::IO::OccGltfReader::Properties @@ -1415,86 +1644,122 @@ It can be disabled in order to minimize the size of the resulting file. Target Format - Ascii - Text + Text - Binary - Binary + Binary + + + + Mayo::IO::OccVrmlWriter::Properties + + + shapeRepresentation + Shape Representation + + + + Mayo::IO::PlyWriterI18N + + + Line that will appear in header + + + + + targetFormat + Target Format + + + + writeColors + Write Colors + + + + defaultColor + Default Color + + + + comment + Comment + + + + Failed to open file + - - - Mayo::IO::OccVrmlWriter::Properties - - shapeRepresentation - Shape Representation + + Unknown host endianness + 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 @@ -1502,108 +1767,99 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::Main - - Mayo, an open-source 3D viewer based on Qt5/OpenCascade + + Theme for the UI(classic|dark) - - Theme for the UI(classic|dark) + + name - - 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) - + files - + Files to open at startup, optionally - + [files...] - - OpenCascade settings file doesn't exist or is not readable [path=%1] + + Execute unit tests and exit application - - OpenCascade settings file could not be loaded with QSettings [path=%1] + + OpenCascade settings file doesn't exist or is not readable [path=%1] - - Failed to load translation file [path=%1] + + OpenCascade settings file could not be loaded with QSettings [path=%1] - - Exported {} + + Failed to load translation file [path=%1] - - Exporting {}... + + Settings file(INI format) to load at startup - - 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...) - - Mesh BRep shapes - - - - - Imported - - - - - Importing... - - - - + Failed to load application settings file [path=%1] - + No input files -> nothing to export - + Failed to load theme '%1' @@ -1699,7 +1955,7 @@ It can be disabled in order to minimize the size of the resulting file. - + Import Import @@ -1730,7 +1986,7 @@ It can be disabled in order to minimize the size of the resulting file. - + Options @@ -1887,105 +2143,160 @@ It can be disabled in order to minimize the size of the resulting file. - + %1 files(%2) %1 is the format identifier and %2 is the file filters string - + All files(*.*) - + Select Part File - + Warning - - + + Error - + About %1 - + Anonymous%1 - - + + Mesh BRep shapes - - + + Import time: {}ms - + Export time: {}ms - + Select Output File - + + Data - + Graphics - + Close %1 - + Close - + Close all except %1 - + Close all except current - + %1 | %2 - + Clear menu + + Mayo::MeasureDisplayI18N + + + Sum + + + + + (<font color="#FF5500">X</font>{0} <font color="#55FF00">Y</font>{1} <font color="#0077FF">Z</font>{2}){3} + + + + + X{0} Y{1} Z{2} + + + + + Diameter: {0}{1} + + + + + Ø{0} + + + + + Min Distance: {0}{1}<br>Point1: {2}<br>Point2: {3} + + + + + Angle: {0}{1} + + + + + + {0}: {1}{2} + + + + + Length + + + + + Area + Area + + Mayo::Mesh_DocumentTreeNodeProperties @@ -2020,33 +2331,33 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::PropertyItemDelegate - + %1d - + %1h - + %1min - + %1s - - + + %1%2 - + ERROR no stringifier for property type '%1' @@ -2054,56 +2365,56 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::QStringUtils - - + + (%1 %2 %3) - + [%1; %2%3; %4] - - + + %1%2 - + B - + KB - - + + MB - - + + Yes - - + + No - + Partially @@ -2193,57 +2504,62 @@ Last modified: %3 Mayo::WidgetGuiDocument - + Fit All - + 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 @@ -2276,28 +2592,28 @@ Select files to load and open as distinct documents - + today %1 - + yersterday %1 - - + + %1 %2 - + %1 days ago %2 - + %1 Size: %2 @@ -2310,119 +2626,189 @@ Read: %5 - Mayo::WidgetModelTree + Mayo::WidgetMeasure - + Form - - Remove from document + + Area Unit - - <unnamed> + + Measure - - - Mayo::WidgetModelTreeBuilder_Xde - - instanceNameFormat - Name Format Of Assembly Instances + + Millimeter(mm) + - - Show {} + + Centimeter(cm) - - Instance + + Meter(m) - - Product + + Inch(in) - - Both + + Foot(ft) - - - Mayo::WidgetPropertiesEditor - - Form + + Yard(yd) - - Property + + Degree(°) - - Value + + Radian(rad) + + + + + Vertex Position + + + + + Circle Center + + + + + Circle Diameter + + + + + Min Distance + + + + + Length + + + + + Square Millimeter(mm²) + + + + + Square Centimeter(cm²) + + + + + Square Meter(m²) + + + + + Square Inch(in²) + + + + + Square Foot(ft²) - - - Mayo::WidgetShapeSelector - - Vertices + + Square Yard(yd²) - - Edges + + Angle - - Wires + + Surface Area - - Faces + + Length Unit - - Shells + + Angle Unit + + + + + Select entities to measure + + + + + Mayo::WidgetModelTree + + + Form + + + + + Remove from document - - Solids + + <unnamed> + + + Mayo::WidgetModelTreeBuilder_Xde + + + instanceNameFormat + Name Format Of Assembly Instances + - - Compounds + + Show {} + + + Mayo::WidgetPropertiesEditor - - Connected solids + + Form - - ?Shapes? + + Property - - Select %1 + + Value @@ -2533,15 +2919,76 @@ Read: %5 Sub + + OccCommon + + + + Undefined + + + + + posYfwd_posZup + + + + + negZfwd_posYup + + + + + Micrometer + + + + + Millimeter + + + + + Centimeter + + + + + Meter + + + + + Kilometer + + + + + Inch + + + + + Foot + + + + + Mile + + + OccStlWriter::Properties + Ascii - Text + Text + Binary - Binary + Binary @@ -2715,4 +3162,22 @@ Read: %5 + + WidgetModelTreeBuilder_Xde + + + Instance + + + + + Product + + + + + Both + + + diff --git a/i18n/mayo_fr.qm b/i18n/mayo_fr.qm index ef59ab7e..aa56f32f 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 a3dad4f1..1702cfe3 100644 --- a/i18n/mayo_fr.ts +++ b/i18n/mayo_fr.ts @@ -4,276 +4,445 @@ AppModule + 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 Mayo::AppModule - + en Anglais - + fr Français - system - Système + Système - units - Unités + Unités - decimalCount - Nombre de décimales + Nombre de décimales - schema - Schéma + Schéma - application - Application + Application - language - Langue + Langue - recentFiles - Fichiers récents + Fichiers récents - lastOpenFolder - Dernier répertoire ouvert + Dernier répertoire ouvert - lastSelectedFormatFilter - Dernier filtre de format sélectionné + Dernier filtre de format sélectionné - linkWithDocumentSelector - Lier au sélecteur de documents + Lier au sélecteur de documents - graphics - Graphisme + Graphisme - defaultShowOriginTrihedron - Afficher le trihèdre Origine par défaut + Afficher le trihèdre Origine par défaut - clipPlanes - Plans de coupe + Plans de coupe - cappingOn - Bouchage + Bouchage - cappingHatchOn - Bouchages avec hachures + Bouchages avec hachures - meshDefaults - Maillage par défauts + Maillage par défauts - meshingQuality - Qualité + Qualité - meshingChordalDeflection - Déflection chordale + Déflection chordale - meshingAngularDeflection - Déflection angulaire + Déflection angulaire - meshingRelative - Relatif + Relatif - instantZoomFactor - Coefficient du zoom instantané + Coefficient du zoom instantané - color - Couleur + Couleur - edgeColor - Couleur des arêtes + Couleur des arêtes - material - Matériau + Matériau - showEgesOn - Afficher les arêtes + Afficher les arêtes - showNodesOn - Afficher les nœuds + Afficher les nœuds - meshing - Maillage BRep + Maillage BRep - 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 + 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 + 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 - For the tesselation 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 + Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne - 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 + Contrôle la précision du maillage calculé à partir de la forme BRep - For the tesselation 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 + Pour la tesselation des faces, la déflection chordale limite la distance entre une courbe et sa discrétisation - 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. - Calcul relatif de la tolérance des arêtes + Calcul relatif de la tolérance des arêtes 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. - 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 + 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 + 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 + Activer le hachage texturé pour le bouchage des graphismes actuellement coupés - import - Import + Import - export - Export + 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 + + + + Mayo::AppModuleProperties + + + language + Langue + + + + system + Système + + + + application + Application + + + + meshing + Maillage BRep + + + + graphics + Graphisme + + + + units + Unités + + + + clipPlanes + Plans de coupe + + + + meshDefaults + Maillage par défauts + + + + import + Import + + + + export + 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 + + + + 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 tesselation 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 tesselation 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 + + + + 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. + Calcul relatif de la tolérance des arêtes + +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 + + + + 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 + + + + decimalCount + Nombre de décimales + + + + schema + Schéma + + + + recentFiles + Fichiers récents + + + + lastOpenFolder + Dernier répertoire ouvert + + + + lastSelectedFormatFilter + Dernier filtre de format sélectionné + + + + linkWithDocumentSelector + Lier au sélecteur de documents + + + + meshingQuality + Qualité + + + + meshingChordalDeflection + Déflection chordale + + + + meshingAngularDeflection + Déflection angulaire + + + + meshingRelative + Relatif + + + + navigationStyle + Style de navigation de la vue + + + + defaultShowOriginTrihedron + Afficher le trihèdre Origine par défaut + + + + instantZoomFactor + Coefficient du zoom instantané + + + + cappingOn + Bouchage + + + + cappingHatchOn + Bouchages avec hachures + + + + color + Couleur + + + + edgeColor + Couleur des arêtes + + + + material + Matériau + + + + showEgesOn + Afficher les arêtes + + + + showNodesOn + Afficher les nœuds Mayo::Application - + Binary Mayo Document Format - + XML Mayo Document Format @@ -282,6 +451,82 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera '%1' ne dispose pas des permissions de lecture + + Mayo::BRepMeasureError + + + Entity must be a vertex + L'entité doit être un sommet + + + + Entity must be a circular edge + L'entité doit être une arête circulaire + + + + Entity must be a shape(BREP) + L'entité doit une forme BREP + + + + Computation of minimum distance failed + Échec du calcul de la distance minimum + + + + All entities must be edges + Toutes les entités doivent être des arêtes + + + + Entity must be a linear edge + L'entité doit une arête linéaire + + + + All entities must be faces + Toutes les entités doivent être des faces + + + + Entities must not be parallel + Les entités ne doivent pas être parallèles + + + + Unknown error + Erreur inconnue + + + + Mayo::CliExport + + + Mesh BRep shapes + Maillage des formes BRep + + + + Imported + Importé + + + + Exported {} + Export de {} terminé + + + + Importing... + Import en cours ... + + + + Exporting {}... + Export de {} en cours ... + + Mayo::DialogAbout @@ -569,64 +814,147 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera - Mayo::GraphicsMeshObjectDriver_ObjectProperties + Mayo::DocumentPropertyGroup + + + filepath + Chemin + + + + fileSize + Taille + + + + createdDateTime + Créé + + + + modifiedDateTime + Modifié + + + + owner + Propriétaire + + + + entityCount + Nombre d'entités + + + + Mayo::GraphicsMeshObjectDriver + + + Mesh_Wireframe + [Maillage] Filaire + + + + Mesh_Shaded + [Maillage] Ombré + + + + Mesh_Shrink + [Maillage] Rétréci + - + color Couleur - + edgeColor Couleur des arêtes - + showEdges Montrer les arêtes - + showNodes Montrer les nœuds + + Mayo::GraphicsMeshObjectDriver_ObjectProperties + + color + Couleur + + + edgeColor + Couleur des arêtes + + + showEdges + Montrer les arêtes + + + showNodes + Montrer les nœuds + + Mayo::GraphicsObjectDriver - Shape_Wireframe - [Forme] Filaire + [Forme] Filaire - Shape_HiddenLineRemoval - [Forme] Suppression des arêtes cachées + [Forme] Suppression des arêtes cachées - Shape_Shaded - [Forme] Ombré + [Forme] Ombré - Shape_ShadedWithFaceBoundary - [Forme] Ombré avec arêtes + [Forme] Ombré avec arêtes - Mesh_Wireframe - [Maillage] Filaire + [Maillage] Filaire - Mesh_Shaded - [Maillage] Ombré + [Maillage] Ombré - Mesh_Shrink - [Maillage] Rétréci + [Maillage] Rétréci + + + + Mayo::GraphicsShapeObjectDriver + + + Shape_Wireframe + [Forme] Filaire + + + + Shape_HiddenLineRemoval + [Forme] Suppression des arêtes cachées + + + + Shape_Shaded + [Forme] Ombré + + + + Shape_ShadedWithFaceBoundary + [Forme] Ombré avec arêtes @@ -675,44 +1003,44 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::IO::GmioAmfWriter::Properties - + Format used when writting `double` values as strings Format à utiliser lors de l'écriture des valeurs de type `double`en chaînes de caractères - + Decimal floating point(ex: 392.65) Décimal à virgule flottante (ex: 392,65) - + Scientific notation(ex: 3.9265E+2) Notation scientifique (ex: 3,9265E+2) - + Use the shortest representation: decimal or scientific Utiliser la notation la plus compacte : décimale ou scientifique - + Maximum number of significant digits when writting `double` values Nombre maximal de chiffres significatifs lors de l'écriture de valeurs de type `double` - + Write AMF document in ZIP archive containing one file entry Écrire le document AMF dans une archive ZIP contenant une entrée de fichier - + Filename of the single AMF entry within the ZIP archive. Only applicable if option `{}` is on Nom de l'entrée du fichier AMF dans l'archive ZIP. Seulement applicable si l'option `{}` est activée - + Use the ZIP64 format extensions. Only applicable if option `{}` is on Utiliser les extensions de format ZIP64. @@ -731,27 +1059,27 @@ Only applicable if option `%1` is on Seulement applicable si l'option `%1` est activée - + float64Format Format nombres flottants 64bit - + float64Precision Précision nombres flottants 64bit - + createZipArchive Créer une archive ZIP - + zipEntryFilename Nom du fichier de l'entrée ZIP - + useZip64 Utiliser les extensions ZIP64 @@ -794,52 +1122,52 @@ Seulement applicable si l'option `%1` est activée Mayo::IO::ImageWriterI18N - + Image width in pixels Largeur de l'image en pixels - + Image height in pixels Hauteur de l'image en pixels - + Camera orientation expressed in Z-up convention as a unit vector 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 @@ -875,60 +1203,48 @@ Seulement applicable si l'option `%1` est activée Mayo::IO::OccCommon - - Undefined - Indéfini + Indéfini - posYfwd_posZup - +Zup + +Zup - negZfwd_posYup - +Yup + +Yup - Micrometer - Micromètre + Micromètre - Millimeter - Millimètre + Millimètre - Centimeter - Centimètre + Centimètre - Meter - Mètre + Mètre - Kilometer - Kilomètre + Kilomètre - Inch - Pouce + Pouce - Foot - Pied + Pied - Mile - Mile + Mile @@ -1095,7 +1411,7 @@ Applicable seulement si l'option `{}` est cochée Option supported from OpenCascade ≥ v7.6 [option={}, actual version={}] - + Option prise en charge à partir de OpenCascade ≥ v7.6 [option={}, version actuelle={}] @@ -1473,14 +1789,12 @@ It can be disabled in order to minimize the size of the resulting file. Format cible - Ascii - Texte + Texte - Binary - Binaire + Binaire @@ -1491,6 +1805,44 @@ It can be disabled in order to minimize the size of the resulting file. Représentation des formes + + Mayo::IO::PlyWriterI18N + + + Line that will appear in header + Texte qui apparaîtra dans l'en-tête + + + + targetFormat + Format cible + + + + writeColors + Écrire la couleur des sommets + + + + defaultColor + Couleur par défaut + + + + comment + Commentaire + + + + Failed to open file + Impossible d'ouvrir le fichier + + + + Unknown host endianness + Boutisme du CPU inconnu + + Mayo::IO::System @@ -1505,45 +1857,45 @@ It can be disabled in order to minimize the size of the resulting file. %2 - + Reading file Lecture fichier - + Unknown format Format inconnu - + Error during import of '{}' {} Erreur pendant l'import de '{}' {} - + No supporting reader Aucun lecteur compatible - + File read problem Problème de lecture fichier - + Transferring file Transfert du fichier - - + + File transfer problem Problème de transfert fichier - + Error during export to '{}' {} Erreur pendant l'export de '{}' @@ -1556,22 +1908,22 @@ It can be disabled in order to minimize the size of the resulting file. %2 - + No supporting writer Aucun writer compatible - + Transfer Transfert - + Write Écriture - + File write problem Problème d'écriture fichier @@ -1579,67 +1931,79 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::Main - Mayo, an open-source 3D viewer based on Qt5/OpenCascade - Mayo, une visionneuse 3D en code libre basée surQt5/OpenCascade + 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) - + 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] - Exported {} - Export de {} terminé + Export de {} terminé - Exporting {}... - Export de {} en cours ... + Export de {} en cours ... - + Failed to load application settings file [path=%1] Échec chargement du fichier de configuration [chemin=%1] @@ -1650,35 +2014,38 @@ 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 ...) - Mesh BRep shapes - Maillage des formes BRep + Maillage des formes BRep - Imported - Importé + Importé - Importing... - Import en cours ... + Import en cours ... Exported %1 @@ -1689,12 +2056,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' @@ -1798,7 +2165,7 @@ It can be disabled in order to minimize the size of the resulting file. - + Import Importer @@ -1829,7 +2196,7 @@ It can be disabled in order to minimize the size of the resulting file. - + Options @@ -1857,7 +2224,7 @@ It can be disabled in order to minimize the size of the resulting file. Alt+Left - + @@ -1986,56 +2353,56 @@ It can be disabled in order to minimize the size of the resulting file. Montrer/cacher les statistiques de rendu - + %1 files(%2) %1 is the format identifier and %2 is the file filters string - %1 fichiers (%2) + %1 fichiers (%2) - + All files(*.*) Tous les fichiers (*.*) - + Select Part File Selectionner fichier pièce - + Warning Avertissement - - + + Error Erreur - + About %1 À propos %1 - + Anonymous%1 Anonyme%1 - - + + Mesh BRep shapes Maillage des formes BRep - - + + Import time: {}ms Durée import: {}ms - + Export time: {}ms Durée export: {}ms @@ -2044,7 +2411,7 @@ It can be disabled in order to minimize the size of the resulting file. Temps import : %1ms - + Select Output File Sélection fichier de sortie @@ -2053,46 +2420,101 @@ It can be disabled in order to minimize the size of the resulting file. Temps export : %1ms - + + Data Données - + Graphics Graphismes - + Close %1 Fermer %1 - + Close Fermer - + Close all except %1 Tout fermer sauf %1 - + Close all except current Tout fermer sauf document courant - + %1 | %2 - + Clear menu Vider le menu + + Mayo::MeasureDisplayI18N + + + Sum + Total + + + + (<font color="#FF5500">X</font>{0} <font color="#55FF00">Y</font>{1} <font color="#0077FF">Z</font>{2}){3} + + + + + X{0} Y{1} Z{2} + + + + + Diameter: {0}{1} + Diamètre: {0}{1} + + + + Ø{0} + + + + + Min Distance: {0}{1}<br>Point1: {2}<br>Point2: {3} + Distance Min: {0}{1}<br>Point1: {2}<br>Point2: {3} + + + + Angle: {0}{1} + + + + + + {0}: {1}{2} + + + + + Length + Longueur + + + + Area + Aire + + Mayo::Mesh_DocumentTreeNodeProperties @@ -2127,33 +2549,33 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::PropertyItemDelegate - + %1d %1j - + %1h %1h - + %1min %1min - + %1s %1s - - + + %1%2 %1%2 - + ERROR no stringifier for property type '%1' ERREUR aucune transformation en string pour les propriétés de type '%1' @@ -2161,56 +2583,56 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::QStringUtils - - + + (%1 %2 %3) (%1 %2 %3) - + [%1; %2%3; %4] [%1; %2%3; %4] - - + + %1%2 %1%2 - + B o - + KB Ko - - + + MB Mo - - + + Yes Oui - - + + No Non - + Partially Partiellement @@ -2310,7 +2732,7 @@ It can be disabled in order to minimize the size of the resulting file. Form - + Form @@ -2335,57 +2757,62 @@ Modifié le: %3 {1 Mayo::WidgetGuiDocument - + Fit All Adapter à tout - + 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 @@ -2423,28 +2850,28 @@ Select files to load and open as distinct documents Selectionnez les fichiers à charger et ouvrir comme documents distincts - + today %1 aujourd'hui %1 - + yersterday %1 hier %1 - - + + %1 %2 %1 %2 - + %1 days ago %2 %1 jours %2 - + %1 Size: %2 @@ -2463,6 +2890,164 @@ Lu: %5 + + Mayo::WidgetMeasure + + + Form + Form + + + + Area Unit + Unité aire + + + + Measure + Mesure + + + + Millimeter(mm) + Millimètre(mm) + + + + Centimeter(cm) + Centimètre(cm) + + + + Meter(m) + Mètre(m) + + + + Inch(in) + Pouce(in) + + + + Foot(ft) + Pied(ft) + + + + Yard(yd) + + + + + Degree(°) + Degré(°) + + + + Radian(rad) + + + + + Vertex Position + Position sommet + + + + Circle Center + Centre cercle + + + + Circle Diameter + Diamètre cercle + + + + Min Distance + Distance min + + + + Length + Longueur + + + + Square Millimeter(mm²) + Millimètre carré(mm²) + + + + Square Centimeter(cm²) + Centimètre carré(cm²) + + + + Square Meter(m²) + Mètre carré(m²) + + + + Square Inch(in²) + Pouce carré(in²) + + + + Square Foot(ft²) + Pied carré(ft²) + + + + Square Yard(yd²) + Yard carré(yd²) + + + + Angle + + + + + Surface Area + Aire surface + + + + Length Unit + Unité longueur + + + Millimeter + Millimètre + + + Centimeter + Centimètre + + + Meter + Mètre + + + Inch + Pouce + + + Foot + Pied + + + + Angle Unit + Unité angle + + + + Select entities to measure + Sélectionner les entités à mesurer + + Mayo::WidgetModelTree @@ -2484,12 +3069,12 @@ Lu: %5 Mayo::WidgetModelTreeBuilder_Xde - + instanceNameFormat Format des noms d'instance - + Show {} Montrer {} @@ -2498,19 +3083,16 @@ Lu: %5 Montrer %1 - Instance - Instance + Instance - Product - Produit + Produit - Both - Les Deux + Les Deux @@ -2531,59 +3113,6 @@ Lu: %5 Valeur - - Mayo::WidgetShapeSelector - - - Vertices - - - - - Edges - - - - - Wires - - - - - Faces - - - - - Shells - - - - - Solids - - - - - Compounds - - - - - Connected solids - - - - - ?Shapes? - - - - - Select %1 - - - Mayo::XCaf_DocumentTreeNodeProperties @@ -2694,59 +3223,73 @@ Lu: %5 OccCommon + + Undefined - Indéfini + Indéfini + posYfwd_posZup - +Zup + +Zup + negZfwd_posYup - +Yup + +Yup + Micrometer - Micromètre + Micromètre + Millimeter - Millimètre + Millimètre + Centimeter - Centimètre + Centimètre + Meter - Mètre + Mètre + Kilometer - Kilomètre + Kilomètre + Inch - Pouce + Pouce + Foot - Pied + Pied + Mile - Mile + Mile OccStlWriter::Properties + Ascii - Texte + Texte + Binary - Binaire + Binaire @@ -2923,16 +3466,19 @@ Lu: %5 WidgetModelTreeBuilder_Xde + Instance - Instance + Instance + Product - Produit + Produit + Both - Les Deux + Les Deux diff --git a/images/appicon.svg b/images/appicon.svg new file mode 100644 index 00000000..9c54c789 --- /dev/null +++ b/images/appicon.svg @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/images/themes/classic/measure.svg b/images/themes/classic/measure.svg new file mode 100644 index 00000000..d954d1ed --- /dev/null +++ b/images/themes/classic/measure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/dark/measure.svg b/images/themes/dark/measure.svg new file mode 100644 index 00000000..d954d1ed --- /dev/null +++ b/images/themes/dark/measure.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/installer/opencascade.conf b/installer/opencascade.conf deleted file mode 100644 index ecb9fed9..00000000 --- a/installer/opencascade.conf +++ /dev/null @@ -1,20 +0,0 @@ -MMGT_OPT=1 -MMGT_CLEAR=1 -MMGT_REENTRANT=0 -CSF_LANGUAGE=us -CSF_EXCEPTION_PROMPT=1 - -CSF_SHMessage=OpenCascade/SHMessage -CSF_MDTVTexturesDirectory=OpenCascade/Textures -CSF_ShadersDirectory=OpenCascade/Shaders -CSF_XSMessage=OpenCascade/XSMessage -CSF_TObjMessage=OpenCascade/TObj -CSF_StandardDefaults=OpenCascade/StdResource -CSF_PluginDefaults=OpenCascade/StdResource -CSF_XCAFDefaults=OpenCascade/StdResource -CSF_TObjDefaults=OpenCascade/StdResource -CSF_StandardLiteDefaults=OpenCascade/StdResource -CSF_IGESDefaults=OpenCascade/XSTEPResource -CSF_STEPDefaults=OpenCascade/XSTEPResource -CSF_XmlOcafResource=OpenCascade/XmlOcafResource -CSF_MIGRATION_TYPES=OpenCascade/StdResource/MigrationSheet.txt diff --git a/installer/qt.conf b/installer/qt.conf deleted file mode 100644 index 2438eff7..00000000 --- a/installer/qt.conf +++ /dev/null @@ -1,2 +0,0 @@ -[Paths] -Plugins = QtPlugins diff --git a/installer/setup.iss b/installer/setup.iss deleted file mode 100644 index d1ece918..00000000 --- a/installer/setup.iss +++ /dev/null @@ -1,76 +0,0 @@ -#include "setupvars.iss" -#include "version.iss" - -[Setup] -ArchitecturesInstallIn64BitMode=x64 -AppId={{F1978C7C-3C90-477F-B634-B99746AA153D} -AppName={#TargetProduct} -AppVerName={#TargetProduct} v{#VersionNumber} -AppPublisher={#TargetCompany} -DefaultGroupName={#TargetCompany} -DefaultDirName={pf}\{#TargetCompany}\{#TargetProduct} -OutputBaseFilename={#TargetName}_v{#VersionNumber}_win{#TargetArch}_installer -Compression=lzma -SolidCompression=yes - -[Icons] -Name: "{group}\{#TargetProduct}"; Filename: "{app}\{#TargetName}.exe"; WorkingDir: "{app}"; IconFileName: "{app}\{#TargetName}.exe" -Name: "{commondesktop}\{#TargetProduct}"; Filename: "{app}\{#TargetName}.exe"; Tasks: desktopicon; WorkingDir: "{app}"; IconFileName: "{app}\{#TargetName}.exe" - -[Languages] -Name: "en"; MessagesFile: "compiler:Default.isl" -Name: "fr"; MessagesFile: "compiler:Languages\French.isl" -[CustomMessages] -en.installVcRuntime = Installing redistributable Visual Studio runtime ... -fr.installVcRuntime = Installation du package redistribuable Visual Studio ... - -[Tasks] -Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked - -; First clear the setup folder (in case of a setup overwrite) -[InstallDelete] -Type: filesandordirs; Name: "{app}\*" - -[Files] -Source: "{#AppBuildDir}\release\{#TargetName}.exe"; DestDir: "{app}"; Flags: ignoreversion - -Source: "{#MsvcRedist_Dir}\vcredist_{#TargetArch}.exe"; DestDir: "{app}"; Flags: deleteafterinstall; -Source: "opencascade.conf"; DestDir: "{app}"; -Source: "qt.conf"; DestDir: "{app}"; - -; Qt5 -Source: "{#QtDir}\bin\Qt5Core.dll"; DestDir: "{app}"; Flags: ignoreversion; -Source: "{#QtDir}\bin\Qt5Gui.dll"; DestDir: "{app}"; Flags: ignoreversion; -Source: "{#QtDir}\bin\Qt5Svg.dll"; DestDir: "{app}"; Flags: ignoreversion; -Source: "{#QtDir}\bin\Qt5Widgets.dll"; DestDir: "{app}"; Flags: ignoreversion; -Source: "{#QtDir}\bin\Qt5WinExtras.dll"; DestDir: "{app}"; Flags: ignoreversion; -Source: "{#QtDir}\plugins\iconengines\qsvgicon.dll"; DestDir: "{app}\QtPlugins\iconengines"; Flags: ignoreversion -Source: "{#QtDir}\plugins\imageformats\qsvg.dll"; DestDir: "{app}\QtPlugins\imageformats"; Flags: ignoreversion -Source: "{#QtDir}\plugins\platforms\qwindows.dll"; DestDir: "{app}\QtPlugins\platforms"; Flags: ignoreversion - -; OpenCascade -#include "opencascade_dlls.iss" - -; OpenCascade resources -Source: "{#OpenCascade_SrcDir}\SHMessage\*.*"; DestDir: "{app}\OpenCascade\SHMessage"; Flags: ignoreversion -Source: "{#OpenCascade_SrcDir}\Shaders\*.*"; DestDir: "{app}\OpenCascade\Shaders"; Flags: ignoreversion -Source: "{#OpenCascade_SrcDir}\StdResource\*.*"; DestDir: "{app}\OpenCascade\StdResource"; Flags: ignoreversion -Source: "{#OpenCascade_SrcDir}\TObj\*.msg"; DestDir: "{app}\OpenCascade\TObj"; Flags: ignoreversion -Source: "{#OpenCascade_SrcDir}\Textures\*.*"; DestDir: "{app}\OpenCascade\Textures"; Flags: ignoreversion -Source: "{#OpenCascade_SrcDir}\XSMessage\*.*"; DestDir: "{app}\OpenCascade\XSMessage"; Flags: ignoreversion -Source: "{#OpenCascade_SrcDir}\XSTEPResource\*.*"; DestDir: "{app}\OpenCascade\XSTEPResource"; Flags: ignoreversion -Source: "{#OpenCascade_SrcDir}\XmlOcafResource\*.*"; DestDir: "{app}\OpenCascade\XmlOcafResource"; Flags: ignoreversion - -; OpenCascade 3rdparty -Source: "{#Tbb_BinDir}\tbb.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#Tbb_BinDir}\tbbmalloc.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#FFMPEG_BinDir}\*.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#FreeImage_BinDir}\freeimage.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#FreeImage_BinDir}\freeimageplus.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#FreeType_BinDir}\freetype.dll"; DestDir: "{app}"; Flags: ignoreversion -Source: "{#OpenVR_BinDir}\*.dll"; DestDir: "{app}"; Flags: ignoreversion -;Source: "{#TclTk_BinDir}\tcl86.dll"; DestDir: "{app}"; Flags: ignoreversion -;Source: "{#TclTk_BinDir}\tk86.dll"; DestDir: "{app}"; Flags: ignoreversion - -[Run] -Filename: "{app}\vcredist_{#TargetArch}.exe"; Parameters: "/q"; StatusMsg: "{cm:installVcRuntime}" diff --git a/installer/setupvars.iss.in b/installer/setupvars.iss.in deleted file mode 100644 index 07d03bf1..00000000 --- a/installer/setupvars.iss.in +++ /dev/null @@ -1,10 +0,0 @@ -#define AppBuildDir \"$$OUT_PWD\" -#define QtDir \"$$[QT_INSTALL_PREFIX]\" -#define OpenCascade_BinDir \"$$CASCADE_BIN_DIR\" -#define OpenCascade_SrcDir \"$$CASCADE_SRC_DIR\" -#define FFMPEG_BinDir \"$$FFMPEG_BIN_DIR\" -#define FreeImage_BinDir \"$$FREEIMAGE_BIN_DIR\" -#define FreeType_BinDir \"$$FREETYPE_BIN_DIR\" -#define Tbb_BinDir \"$$TBB_BIN_DIR\" -#define OpenVR_BinDir \"$$OPENVR_BIN_DIR\" -#define MsvcRedist_Dir \"$$(VCToolsRedistDir)\" diff --git a/installer/version.iss.in b/installer/version.iss.in deleted file mode 100644 index bb75294d..00000000 --- a/installer/version.iss.in +++ /dev/null @@ -1,5 +0,0 @@ -#define VersionNumber \"$$MAYO_VERSION\" -#define TargetName \"$$TARGET\" -#define TargetArch \"$$VERSION_TARGET_ARCH\" -#define TargetProduct \"$$QMAKE_TARGET_PRODUCT\" -#define TargetCompany \"$$QMAKE_TARGET_COMPANY\" diff --git a/mayo.pro b/mayo.pro index b321bad9..1fd67082 100644 --- a/mayo.pro +++ b/mayo.pro @@ -13,12 +13,14 @@ CONFIG(debug, debug|release) { message(Mayo version $$MAYO_VERSION release) } -QT += core gui widgets -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00 message(Qt version $$QT_VERSION) +QT += core gui widgets +greaterThan(QT_MAJOR_VERSION, 5) { + QT += openglwidgets +} + CONFIG += c++17 -CONFIG += file_copies CONFIG(debug, debug|release) { CONFIG += console } else { @@ -26,6 +28,10 @@ CONFIG(debug, debug|release) { CONFIG += release_with_debuginfo } +DEFINES += \ + QT_DISABLE_DEPRECATED_BEFORE=0x050F00 \ + QT_IMPLICIT_QFILEINFO_CONSTRUCTION + release_with_debuginfo:msvc { # https://docs.microsoft.com/en-us/cpp/build/reference/how-to-debug-a-release-build QMAKE_CXXFLAGS_RELEASE += /Zi @@ -49,6 +55,7 @@ clang { LIBS += -lc++fs } macx { + DEFINES += GL_SILENCE_DEPRECATION QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 # QMAKE_CXXFLAGS += -mmacosx-version-min=10.15 } @@ -65,6 +72,7 @@ HEADERS += \ $$files(src/io_occ/*.h) \ $$files(src/io_dxf/*.h) \ $$files(src/io_image/*.h) \ + $$files(src/io_ply/*.h) \ $$files(src/graphics/*.h) \ $$files(src/gui/*.h) \ $$files(src/app/*.h) \ @@ -74,23 +82,17 @@ SOURCES += \ $$files(src/io_occ/*.cpp) \ $$files(src/io_dxf/*.cpp) \ $$files(src/io_image/*.cpp) \ + $$files(src/io_ply/*.cpp) \ $$files(src/graphics/*.cpp) \ $$files(src/gui/*.cpp) \ $$files(src/app/*.cpp) \ \ src/3rdparty/fmt/src/format.cc \ -win32 { +win32:lessThan(QT_MAJOR_VERSION, 6) { QT += winextras HEADERS += $$files(src/app/windows/*.h) SOURCES += $$files(src/app/windows/*.cpp) - - COPIES += WinInstallerFiles - WinInstallerFiles.files = $$files($$PWD/installer/*.iss) - WinInstallerFiles.files += $$files($$PWD/installer/*.conf) - WinInstallerFiles.path = $$OUT_PWD/installer - - QMAKE_SUBSTITUTES += $$PWD/installer/setupvars.iss.in } FORMS += $$files(src/app/*.ui) @@ -101,8 +103,9 @@ RC_ICONS = images/appicon.ico OTHER_FILES += \ README.md \ appveyor.yml \ - .travis.yml \ - images/credits.txt + .github/workflows/ci.yml \ + images/credits.txt \ + scripts/bump-version.rb \ # OpenCascade include(opencascade.pri) @@ -171,41 +174,6 @@ minOpenCascadeVersion(7, 4, 0) { # -- VRML support LIBS += -lTKVRML -CASCADE_LIST_OPTBIN_DIR = $$split(CASCADE_OPTBIN_DIRS, ;) -for(binPath, CASCADE_LIST_OPTBIN_DIR) { - lowerBinPath = $$lower($${binPath}) - - findLib = $$find(lowerBinPath, "ffmpeg") - !isEmpty(findLib):FFMPEG_BIN_DIR = $${binPath} - - findLib = $$find(lowerBinPath, "freeimage") - !isEmpty(findLib):FREEIMAGE_BIN_DIR = $${binPath} - - findLib = $$find(lowerBinPath, "freetype") - !isEmpty(findLib):FREETYPE_BIN_DIR = $${binPath} - - findLib = $$find(lowerBinPath, "tbb") - !isEmpty(findLib):TBB_BIN_DIR = $${binPath} - - findLib = $$find(lowerBinPath, "openvr") - !isEmpty(findLib):OPENVR_BIN_DIR = $${binPath} -} - -# -- Create file "opencascade_dlls.iss" that will contain the required OpenCascade DLL files to be -# -- added in the InnoSetup [Files] section -# -- The list of OpenCascade libraries is retrieved from the LIBS QMake variable -win32 { - for(lib, LIBS) { - findTK = $$find(lib, "-lTK") - !isEmpty(findTK) { - lib = $$replace(lib, "-l", "") - CASCADE_INNOSETUP_DLLS += "Source: \"$$CASCADE_BIN_DIR\\$${lib}.dll\"; DestDir: \"{app}\"; Flags: ignoreversion" - } - } - - write_file($$OUT_PWD/installer/opencascade_dlls.iss, CASCADE_INNOSETUP_DLLS) -} - # gmio isEmpty(GMIO_ROOT) { message(gmio OFF) @@ -229,3 +197,14 @@ isEmpty(GMIO_ROOT) { $$GMIO_ROOT/src/gmio_support/stream_qt.cpp DEFINES += HAVE_GMIO } + +# Unit tests +CONFIG(withtests) { + include(tests/tests.pri) + DEFINES += MAYO_WITH_TESTS +} + +# Developer custom processing +exists($$PWD/custom.pri) { + include($$PWD/custom.pri) +} diff --git a/mayo.qrc b/mayo.qrc index d956e0e7..d59ddd42 100644 --- a/mayo.qrc +++ b/mayo.qrc @@ -76,5 +76,7 @@ images/themes/dark/multiple.svg images/themes/classic/reload.svg images/themes/dark/reload.svg + images/themes/classic/measure.svg + images/themes/dark/measure.svg diff --git a/opencascade.pri b/opencascade.pri index ff2678ab..76544374 100644 --- a/opencascade.pri +++ b/opencascade.pri @@ -47,7 +47,7 @@ equals(QT_ARCH, i386) { } else:equals(QT_ARCH, x86_64) { DEFINES += _OCC64 } else { - error(Platform architecture 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)) diff --git a/scripts/bump-version.rb b/scripts/bump-version.rb index 4a3d5ceb..13a54c90 100644 --- a/scripts/bump-version.rb +++ b/scripts/bump-version.rb @@ -6,14 +6,13 @@ #!/usr/bin/ruby -if ARGV.empty? then +if ARGV.empty? puts "Error: no version argument" return end -version_str = ARGV.first version_array = ARGV.first.split('.') -if version_array.size != 3 then +if version_array.size != 3 puts "Error: wrong version format(maj.min.patch)" return end diff --git a/scripts/travis-build.sh b/scripts/travis-build.sh deleted file mode 100644 index df4ff23d..00000000 --- a/scripts/travis-build.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/bin/bash - -# Make the script fails on any command error -set -e - -# Force QMake spec in case of Clang -if [ "${TRAVIS_COMPILER}" == "clang" ]; then - export QMAKESPEC=linux-clang-libc++ -fi - -echo QMAKESPEC=${QMAKESPEC} - -# Create out-of-source dir -mkdir build -cd build - -# Run QMake -qmake --version -qmake ../mayo.pro CASCADE_INC_DIR=/usr/include/opencascade - -# Make -make -j2 diff --git a/src/app/app_module.cpp b/src/app/app_module.cpp index e3f3f7f8..5a33e23d 100644 --- a/src/app/app_module.cpp +++ b/src/app/app_module.cpp @@ -6,15 +6,13 @@ #include "app_module.h" -#include "../base/application.h" #include "../base/bnd_utils.h" #include "../base/brep_utils.h" +#include "../base/cpp_utils.h" #include "../base/io_reader.h" #include "../base/io_writer.h" #include "../base/io_system.h" -#include "../base/occt_enums.h" #include "../base/settings.h" -#include "../graphics/graphics_object_driver.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" #include "qtcore_utils.h" @@ -32,25 +30,9 @@ namespace Mayo { -static inline const Enumeration enumLanguages = { - { 0, AppModule::textId("en") }, - { 1, AppModule::textId("fr") } -}; - -AppModule::AppModule(Application* app) - : QObject(app), - PropertyGroup(app->settings()), - m_app(app), - groupId_system(app->settings()->addGroup(textId("system"))), - sectionId_systemUnits(app->settings()->addSection(this->groupId_system, textId("units"))), - groupId_application(app->settings()->addGroup(textId("application"))), - language(this, textId("language"), enumLanguages), - groupId_meshing(app->settings()->addGroup(textId("meshing"))), - groupId_graphics(app->settings()->addGroup(textId("graphics"))), - sectionId_graphicsClipPlanes( - app->settings()->addSection(this->groupId_graphics, textId("clipPlanes"))), - sectionId_graphicsMeshDefaults( - app->settings()->addSection(this->groupId_graphics, textId("meshDefaults"))), +AppModule::AppModule() + : m_settings(new Settings(this)), + m_props(m_settings), m_locale(QLocale::system()) { static bool metaTypesRegistered = false; @@ -59,146 +41,15 @@ AppModule::AppModule(Application* app) metaTypesRegistered = true; } - auto settings = app->settings(); - - // System - // -- Units - settings->addSetting(&this->unitSystemSchema, this->sectionId_systemUnits); - settings->addSetting(&this->unitSystemDecimals, this->sectionId_systemUnits); - this->unitSystemDecimals.setRange(1, 99); - this->unitSystemDecimals.setSingleStep(1); - this->unitSystemDecimals.setConstraintsEnabled(true); - - // Application - this->language.setDescription( - textIdTr("Language used for the application. Change will take effect after application restart")); - this->linkWithDocumentSelector.setDescription( - textIdTr("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")); - settings->addSetting(&this->language, this->groupId_application); - settings->addSetting(&this->recentFiles, this->groupId_application); - settings->addSetting(&this->lastOpenDir, this->groupId_application); - settings->addSetting(&this->lastSelectedFormatFilter, this->groupId_application); - settings->addSetting(&this->linkWithDocumentSelector, this->groupId_application); - this->recentFiles.setUserVisible(false); - this->lastOpenDir.setUserVisible(false); - this->lastSelectedFormatFilter.setUserVisible(false); - - // Meshing - this->meshingQuality.setDescription( - textIdTr("Controls precision of the mesh to be computed from the BRep shape")); - this->meshingQuality.mutableEnumeration().changeTrContext(AppModule::textIdContext()); - this->meshingChordalDeflection.setDescription( - textIdTr("For the tesselation of faces the chordal deflection limits the distance between " - "a curve and its tessellation")); - this->meshingAngularDeflection.setDescription( - textIdTr("For the tesselation of faces the angular deflection limits the angle between " - "subsequent segments in a polyline")); - this->meshingRelative.setDescription( - textIdTr("Relative computation of edge tolerance\n\n" - "If activated, deflection used for the polygonalisation of each edge will be " - "`ChordalDeflection` × `SizeOfEdge`. The deflection used for the faces will be " - "the maximum deflection of their edges.")); - settings->addSetting(&this->meshingQuality, this->groupId_meshing); - settings->addSetting(&this->meshingChordalDeflection, this->groupId_meshing); - settings->addSetting(&this->meshingAngularDeflection, this->groupId_meshing); - settings->addSetting(&this->meshingRelative, this->groupId_meshing); - - // Graphics - this->defaultShowOriginTrihedron.setDescription( - textIdTr("Show or hide by default the trihedron centered at world origin. " - "This doesn't affect 3D view of currently opened documents")); - settings->addSetting(&this->defaultShowOriginTrihedron, this->groupId_graphics); - settings->addSetting(&this->instantZoomFactor, this->groupId_graphics); - // -- Clip planes - this->clipPlanesCappingOn.setDescription( - textIdTr("Enable capping of currently clipped graphics")); - this->clipPlanesCappingHatchOn.setDescription( - textIdTr("Enable capping hatch texture of currently clipped graphics")); - settings->addSetting(&this->clipPlanesCappingOn, this->sectionId_graphicsClipPlanes); - settings->addSetting(&this->clipPlanesCappingHatchOn, this->sectionId_graphicsClipPlanes); - // -- Mesh defaults - settings->addSetting(&this->meshDefaultsColor, this->sectionId_graphicsMeshDefaults); - settings->addSetting(&this->meshDefaultsEdgeColor, this->sectionId_graphicsMeshDefaults); - settings->addSetting(&this->meshDefaultsMaterial, this->sectionId_graphicsMeshDefaults); - settings->addSetting(&this->meshDefaultsShowEdges, this->sectionId_graphicsMeshDefaults); - settings->addSetting(&this->meshDefaultsShowNodes, this->sectionId_graphicsMeshDefaults); - // Import - auto groupId_Import = settings->addGroup(textId("import")); - for (IO::Format format : app->ioSystem()->readerFormats()) { - auto sectionId_format = settings->addSection(groupId_Import, IO::formatIdentifier(format)); - const IO::FactoryReader* factory = app->ioSystem()->findFactoryReader(format); - std::unique_ptr ptrGroup = factory->createProperties(format, settings); - if (ptrGroup) { - for (Property* property : ptrGroup->properties()) - settings->addSetting(property, sectionId_format); - - PropertyGroup* rawPtrGroup = ptrGroup.get(); - settings->addResetFunction(sectionId_format, [=]{ rawPtrGroup->restoreDefaults(); }); - m_mapFormatReaderParameters.insert({ format, rawPtrGroup }); - m_vecPtrPropertyGroup.push_back(std::move(ptrGroup)); - } - } - - // Export - auto groupId_Export = settings->addGroup(textId("export")); - for (IO::Format format : app->ioSystem()->writerFormats()) { - auto sectionId_format = settings->addSection(groupId_Export, IO::formatIdentifier(format)); - const IO::FactoryWriter* factory = app->ioSystem()->findFactoryWriter(format); - std::unique_ptr ptrGroup = factory->createProperties(format, settings); - if (ptrGroup) { - for (Property* property : ptrGroup->properties()) - settings->addSetting(property, sectionId_format); - - PropertyGroup* rawPtrGroup = ptrGroup.get(); - settings->addResetFunction(sectionId_format, [=]{ rawPtrGroup->restoreDefaults(); }); - m_mapFormatWriterParameters.insert({ format, ptrGroup.get() }); - m_vecPtrPropertyGroup.push_back(std::move(ptrGroup)); - } - } - - // Register reset functions - settings->addResetFunction(this->sectionId_systemUnits, [=]{ - this->unitSystemDecimals.setValue(2); - this->unitSystemSchema.setValue(UnitSystem::SI); - }); - settings->addResetFunction(this->groupId_application, [&]{ - this->language.setValue(enumLanguages.findValue("en")); - this->recentFiles.setValue({}); - this->lastOpenDir.setValue({}); - this->lastSelectedFormatFilter.setValue({}); - this->linkWithDocumentSelector.setValue(true); - }); - settings->addResetFunction(this->groupId_graphics, [=]{ - this->defaultShowOriginTrihedron.setValue(true); - this->instantZoomFactor.setValue(5.); - }); - settings->addResetFunction(this->groupId_meshing, [&]{ - this->meshingQuality.setValue(BRepMeshQuality::Normal); - this->meshingChordalDeflection.setQuantity(1 * Quantity_Millimeter); - this->meshingAngularDeflection.setQuantity(20 * Quantity_Degree); - this->meshingRelative.setValue(false); - }); - settings->addResetFunction(this->sectionId_graphicsClipPlanes, [=]{ - this->clipPlanesCappingOn.setValue(true); - this->clipPlanesCappingHatchOn.setValue(true); - }); - settings->addResetFunction(this->sectionId_graphicsMeshDefaults, [=]{ - const GraphicsMeshObjectDriver::DefaultValues meshDefaults; - this->meshDefaultsColor.setValue(meshDefaults.color); - this->meshDefaultsEdgeColor.setValue(meshDefaults.edgeColor); - this->meshDefaultsMaterial.setValue(meshDefaults.material); - this->meshDefaultsShowEdges.setValue(meshDefaults.showEdges); - this->meshDefaultsShowNodes.setValue(meshDefaults.showNodes); - }); + m_settings->setPropertyValueConversion(this); } QStringUtils::TextOptions AppModule::defaultTextOptions() const { QStringUtils::TextOptions opts; opts.locale = this->locale(); - opts.unitDecimals = this->unitSystemDecimals; - opts.unitSchema = this->unitSystemSchema; + opts.unitDecimals = m_props.unitSystemDecimals; + opts.unitSchema = m_props.unitSystemSchema; return opts; } @@ -207,22 +58,28 @@ const QLocale& AppModule::locale() const return m_locale; } -QString AppModule::qmFilePath(const QByteArray& languageCode) +const Enumeration& AppModule::languages() { - return QString(":/i18n/mayo_%1.qm").arg(QString::fromUtf8(languageCode)); + static const Enumeration langs = { + { 0, AppModule::textId("en") }, + { 1, AppModule::textId("fr") } + }; + return langs; } -QByteArray AppModule::languageCode(const ApplicationPtr& app) +QString AppModule::languageCode() const { const char keyLang[] = "application/language"; - const Settings::Variant code = app->settings()->findValueFromKey(keyLang); + const Settings::Variant code = m_settings->findValueFromKey(keyLang); + const Enumeration& langs = AppModule::languages(); if (code.isConvertibleToConstRefString()) { const std::string& strCode = code.toConstRefString(); - if (enumLanguages.contains(strCode)) - return QByteArray::fromStdString(strCode); + if (langs.contains(strCode)) + return QString::fromStdString(strCode); } - return QtCoreUtils::QByteArray_frowRawData(enumLanguages.findName(0)); + std::string_view langDefault = langs.findNameByValue(0); + return QString::fromUtf8(langDefault.data(), CppUtils::safeStaticCast(langDefault.size())); } bool AppModule::excludeSettingPredicate(const Property& prop) @@ -232,14 +89,14 @@ bool AppModule::excludeSettingPredicate(const Property& prop) const PropertyGroup* AppModule::findReaderParameters(IO::Format format) const { - auto it = m_mapFormatReaderParameters.find(format); - return it != m_mapFormatReaderParameters.cend() ? it->second : nullptr; + auto it = m_props.m_mapFormatReaderParameters.find(format); + return it != m_props.m_mapFormatReaderParameters.cend() ? it->second : nullptr; } const PropertyGroup* AppModule::findWriterParameters(IO::Format format) const { - auto it = m_mapFormatWriterParameters.find(format); - return it != m_mapFormatWriterParameters.cend() ? it->second : nullptr; + auto it = m_props.m_mapFormatWriterParameters.find(format); + return it != m_props.m_mapFormatWriterParameters.cend() ? it->second : nullptr; } Settings::Variant AppModule::toVariant(const Property& prop) const @@ -299,10 +156,10 @@ void AppModule::clearMessageLog() void AppModule::prependRecentFile(const FilePath& fp) { const RecentFile* ptrRecentFile = this->findRecentFile(fp); - RecentFiles newRecentFiles = this->recentFiles.value(); + RecentFiles newRecentFiles = m_props.recentFiles.value(); if (ptrRecentFile) { RecentFile& firstRecentFile = newRecentFiles.front(); - RecentFile& recentFile = newRecentFiles.at(ptrRecentFile - &this->recentFiles.value().front()); + RecentFile& recentFile = newRecentFiles.at(ptrRecentFile - &m_props.recentFiles.value().front()); std::swap(firstRecentFile, recentFile); } else { @@ -314,12 +171,12 @@ void AppModule::prependRecentFile(const FilePath& fp) newRecentFiles.pop_back(); } - this->recentFiles.setValue(newRecentFiles); + m_props.recentFiles.setValue(newRecentFiles); } const RecentFile* AppModule::findRecentFile(const FilePath& fp) const { - const RecentFiles& listRecentFile = this->recentFiles.value(); + const RecentFiles& listRecentFile = m_props.recentFiles.value(); auto itFound = std::find_if( listRecentFile.cbegin(), @@ -341,7 +198,7 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) " Function: {}\n Document: {}\n RecentFilesCount: {}", Q_FUNC_INFO, guiDoc->document()->filePath().u8string(), - this->recentFiles.value().size()) + m_props.recentFiles.value().size()) .c_str(); return; } @@ -354,11 +211,11 @@ void AppModule::recordRecentFileThumbnail(GuiDocument* guiDoc) if (!okRecord) return; - const RecentFiles& listRecentFile = this->recentFiles.value(); + const RecentFiles& listRecentFile = m_props.recentFiles.value(); RecentFiles newListRecentFile = listRecentFile; const auto indexRecentFile = std::distance(&listRecentFile.front(), recentFile); newListRecentFile.at(indexRecentFile) = newRecentFile; - this->recentFiles.setValue(newListRecentFile); + m_props.recentFiles.setValue(newListRecentFile); } void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) @@ -366,7 +223,7 @@ void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) if (!guiApp) return; - const RecentFiles& listRecentFile = this->recentFiles.value(); + const RecentFiles& listRecentFile = m_props.recentFiles.value(); RecentFiles newListRecentFile = listRecentFile; for (GuiDocument* guiDoc : guiApp->guiDocuments()) { const RecentFile* recentFile = this->findRecentFile(guiDoc->document()->filePath()); @@ -380,7 +237,7 @@ void AppModule::recordRecentFileThumbnails(GuiApplication* guiApp) } } - this->recentFiles.setValue(newListRecentFile); + m_props.recentFiles.setValue(newListRecentFile); } static QuantityLength shapeChordalDeflection(const TopoDS_Shape& shape) @@ -409,15 +266,17 @@ static QuantityLength shapeChordalDeflection(const TopoDS_Shape& shape) OccBRepMeshParameters AppModule::brepMeshParameters(const TopoDS_Shape& shape) const { + using BRepMeshQuality = AppModuleProperties::BRepMeshQuality; + OccBRepMeshParameters params; params.InParallel = true; #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) params.AllowQualityDecrease = true; #endif - if (this->meshingQuality == BRepMeshQuality::UserDefined) { - params.Deflection = UnitSystem::meters(this->meshingChordalDeflection.quantity()); - params.Angle = UnitSystem::radians(this->meshingAngularDeflection.quantity()); - params.Relative = this->meshingRelative; + if (m_props.meshingQuality == BRepMeshQuality::UserDefined) { + params.Deflection = UnitSystem::meters(m_props.meshingChordalDeflection.quantity()); + params.Angle = UnitSystem::radians(m_props.meshingAngularDeflection.quantity()); + params.Relative = m_props.meshingRelative; } else { struct Coefficients { @@ -435,7 +294,7 @@ OccBRepMeshParameters AppModule::brepMeshParameters(const TopoDS_Shape& shape) c } return { 1, 1 }; }; - const Coefficients coeffs = fnCoefficients(this->meshingQuality); + const Coefficients coeffs = fnCoefficients(m_props.meshingQuality); params.Deflection = UnitSystem::meters(coeffs.chordalDeflection * shapeChordalDeflection(shape)); params.Angle = UnitSystem::radians(coeffs.angularDeflection * (20 * Quantity_Degree)); } @@ -454,38 +313,25 @@ void AppModule::computeBRepMesh(const TDF_Label& labelEntity, TaskProgress* prog this->computeBRepMesh(XCaf::shape(labelEntity), progress); } -AppModule* AppModule::get(const ApplicationPtr& app) +void AppModule::addPropertiesProvider(std::unique_ptr ptr) { - if (app) - return app->findChild(QString(), Qt::FindDirectChildrenOnly); - - return nullptr; + m_vecDocTreeNodePropsProvider.push_back(std::move(ptr)); } -void AppModule::onPropertyChanged(Property* prop) +std::unique_ptr AppModule::properties(const DocumentTreeNode& treeNode) const { - if (prop == &this->meshDefaultsColor - || prop == &this->meshDefaultsEdgeColor - || prop == &this->meshDefaultsMaterial - || prop == &this->meshDefaultsShowEdges - || prop == &this->meshDefaultsShowNodes) - { - auto values = GraphicsMeshObjectDriver::defaultValues(); - values.color = this->meshDefaultsColor.value(); - values.edgeColor = this->meshDefaultsEdgeColor.value(); - values.material = static_cast(this->meshDefaultsMaterial.value()); - values.showEdges = this->meshDefaultsShowEdges.value(); - values.showNodes = this->meshDefaultsShowNodes.value(); - GraphicsMeshObjectDriver::setDefaultValues(values); - } - else if (prop == &this->meshingQuality) { - const bool isUserDefined = this->meshingQuality.value() == BRepMeshQuality::UserDefined; - this->meshingChordalDeflection.setEnabled(isUserDefined); - this->meshingAngularDeflection.setEnabled(isUserDefined); - this->meshingRelative.setEnabled(isUserDefined); + for (const auto& provider : m_vecDocTreeNodePropsProvider) { + if (provider->supports(treeNode)) + return provider->properties(treeNode); } - PropertyGroup::onPropertyChanged(prop); + return std::unique_ptr(); +} + +AppModule* AppModule::get() +{ + static AppModule appModule; + return &appModule; } } // namespace Mayo diff --git a/src/app/app_module.h b/src/app/app_module.h index 21025f19..f96dbd20 100644 --- a/src/app/app_module.h +++ b/src/app/app_module.h @@ -6,27 +6,20 @@ #pragma once -#include "recent_files.h" +#include "app_module_properties.h" +#include "qstring_utils.h" -#include "../base/application_ptr.h" +#include "../base/document_tree_node_properties_provider.h" #include "../base/io_parameters_provider.h" +#include "../base/io_system.h" #include "../base/messenger.h" #include "../base/occ_brep_mesh_parameters.h" -#include "../base/occt_enums.h" -#include "../base/property.h" -#include "../base/property_builtins.h" -#include "../base/property_enumeration.h" #include "../base/property_value_conversion.h" #include "../base/settings.h" -#include "../base/settings_index.h" #include "../base/unit_system.h" -#include "qtcore_hfuncs.h" -#include "qstring_utils.h" #include #include -#include -#include class TDF_Label; class TopoDS_Shape; @@ -37,9 +30,10 @@ class GuiApplication; class GuiDocument; class TaskProgress; +// Provides the root application object as a singleton +// Implements also the behavior specific to the application class AppModule : public QObject, - public PropertyGroup, public IO::ParametersProvider, public PropertyValueConversion, public Messenger @@ -47,94 +41,84 @@ class AppModule : Q_OBJECT MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::AppModule) public: + // Loggable message struct Message { MessageType type; QString text; }; - AppModule(Application* app); - static AppModule* get(const ApplicationPtr& app); + // Query singleton instance + static AppModule* get(); + + // Settings + const AppModuleProperties* properties() const { return &m_props; } + AppModuleProperties* properties() { return &m_props; } + Settings* settings() const { return m_settings; } + + // Predicate suitable to Settings::loadFrom() and Settings::saveAs() + static bool excludeSettingPredicate(const Property& prop); + // Text options corresponding to the active locale/units config QStringUtils::TextOptions defaultTextOptions() const; + + // Current locale used by the application const QLocale& locale() const; - static QString qmFilePath(const QByteArray& languageCode); - static QByteArray languageCode(const ApplicationPtr& app); + // Available supported languages + static const Enumeration& languages(); - static bool excludeSettingPredicate(const Property& prop); + // Short-name of the current language in use(eg. en=english) + QString languageCode() const; + + // Logging + void clearMessageLog(); + Span messageLog() const { return m_messageLog; } + Q_SIGNAL void message(Messenger::MessageType msgType, const QString& text); + Q_SIGNAL void messageLogCleared(); + // Recent files void prependRecentFile(const FilePath& fp); const RecentFile* findRecentFile(const FilePath& fp) const; void recordRecentFileThumbnail(GuiDocument* guiDoc); void recordRecentFileThumbnails(GuiApplication* guiApp); QSize recentFileThumbnailSize() const { return { 190, 150 }; } + // Meshing of BRep shapes OccBRepMeshParameters brepMeshParameters(const TopoDS_Shape& shape) const; void computeBRepMesh(const TopoDS_Shape& shape, TaskProgress* progress = nullptr); void computeBRepMesh(const TDF_Label& labelEntity, TaskProgress* progress = nullptr); - // from IO::ParametersProvider + // Providers to query document tree node properties + void addPropertiesProvider(std::unique_ptr ptr); + std::unique_ptr properties(const DocumentTreeNode& treeNode) const; + + // IO::System object + const IO::System* ioSystem() const { return &m_ioSystem; } + IO::System* ioSystem() { return &m_ioSystem; } + + // -- from IO::ParametersProvider const PropertyGroup* findReaderParameters(IO::Format format) const override; const PropertyGroup* findWriterParameters(IO::Format format) const override; - // from PropertyValueConversion + // -- from PropertyValueConversion Settings::Variant toVariant(const Property& prop) const override; bool fromVariant(Property* prop, const Settings::Variant& variant) const override; - // from Messenger + // -- from Messenger void emitMessage(MessageType msgType, std::string_view text) override; - void clearMessageLog(); - Span messageLog() const { return m_messageLog; } - Q_SIGNAL void message(Messenger::MessageType msgType, const QString& text); - Q_SIGNAL void messageLogCleared(); - - // System - const Settings_GroupIndex groupId_system; - const Settings_SectionIndex sectionId_systemUnits; - PropertyInt unitSystemDecimals{ this, textId("decimalCount") }; - PropertyEnum unitSystemSchema{ this, textId("schema") }; - // Application - const Settings_GroupIndex groupId_application; - PropertyEnumeration language; - PropertyRecentFiles recentFiles{ this, textId("recentFiles") }; - PropertyFilePath lastOpenDir{ this, textId("lastOpenFolder") }; - PropertyString lastSelectedFormatFilter{ this, textId("lastSelectedFormatFilter") }; - PropertyBool linkWithDocumentSelector{ this, textId("linkWithDocumentSelector") }; - // Meshing - const Settings_GroupIndex groupId_meshing; - enum class BRepMeshQuality { VeryCoarse, Coarse, Normal, Precise, VeryPrecise, UserDefined }; - PropertyEnum meshingQuality{ this, textId("meshingQuality") }; - PropertyLength meshingChordalDeflection{ this, textId("meshingChordalDeflection") }; - PropertyAngle meshingAngularDeflection{ this, textId("meshingAngularDeflection") }; - PropertyBool meshingRelative{ this, textId("meshingRelative") }; - // Graphics - const Settings_GroupIndex groupId_graphics; - PropertyBool defaultShowOriginTrihedron{ this, textId("defaultShowOriginTrihedron") }; - PropertyDouble instantZoomFactor{ this, textId("instantZoomFactor") }; - // -- ClipPlanes - const Settings_SectionIndex sectionId_graphicsClipPlanes; - PropertyBool clipPlanesCappingOn{ this, textId("cappingOn") }; - PropertyBool clipPlanesCappingHatchOn{ this, textId("cappingHatchOn") }; - // -- MeshDefaults - const Settings_SectionIndex sectionId_graphicsMeshDefaults; - PropertyOccColor meshDefaultsColor{ this, textId("color") }; - PropertyOccColor meshDefaultsEdgeColor{ this, textId("edgeColor") }; - PropertyEnumeration meshDefaultsMaterial{ this, textId("material"), OcctEnums::Graphic3d_NameOfMaterial() }; - PropertyBool meshDefaultsShowEdges{ this, textId("showEgesOn") }; - PropertyBool meshDefaultsShowNodes{ this, textId("showNodesOn") }; - -protected: - // from PropertyGroup - void onPropertyChanged(Property* prop) override; private: - Application* m_app = nullptr; - std::vector> m_vecPtrPropertyGroup; - std::unordered_map m_mapFormatReaderParameters; - std::unordered_map m_mapFormatWriterParameters; + AppModule(); + AppModule(const AppModule&) = delete; // Not copyable + AppModule& operator=(const AppModule&) = delete; // Not copyable + + Settings* m_settings = nullptr; + IO::System m_ioSystem; + AppModuleProperties m_props; std::vector m_messageLog; std::mutex m_mutexMessageLog; QLocale m_locale; + std::vector> m_vecDocTreeNodePropsProvider; }; } // namespace Mayo diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp new file mode 100644 index 00000000..823b9927 --- /dev/null +++ b/src/app/app_module_properties.cpp @@ -0,0 +1,205 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "app_module_properties.h" +#include "app_module.h" + +#include "../base/io_reader.h" +#include "../base/io_writer.h" +#include "../base/io_system.h" +#include "../base/settings.h" +#include "../base/unit_system.h" +#include "../graphics/graphics_mesh_object_driver.h" + +namespace Mayo { + +AppModuleProperties::AppModuleProperties(Settings* settings) + : PropertyGroup(settings), + groupId_system(settings->addGroup(textId("system"))), + groupId_application(settings->addGroup(textId("application"))), + language(this, textId("language"), &AppModule::languages()), + m_settings(settings) +{ + const auto groupId_meshing = settings->addGroup(textId("meshing")); + const auto groupId_graphics = settings->addGroup(textId("graphics")); + + const auto sectionId_systemUnits = settings->addSection(this->groupId_system, textId("units")); + const auto sectionId_graphicsClipPlanes = settings->addSection(groupId_graphics, textId("clipPlanes")); + const auto sectionId_graphicsMeshDefaults = settings->addSection(groupId_graphics, textId("meshDefaults")); + + this->retranslate(); + + // System + // -- Units + settings->addSetting(&this->unitSystemSchema, sectionId_systemUnits); + settings->addSetting(&this->unitSystemDecimals, sectionId_systemUnits); + this->unitSystemDecimals.setRange(1, 99); + this->unitSystemDecimals.setSingleStep(1); + this->unitSystemDecimals.setConstraintsEnabled(true); + + // Application + settings->addSetting(&this->language, groupId_application); + settings->addSetting(&this->recentFiles, groupId_application); + settings->addSetting(&this->lastOpenDir, groupId_application); + settings->addSetting(&this->lastSelectedFormatFilter, groupId_application); + settings->addSetting(&this->linkWithDocumentSelector, groupId_application); + this->recentFiles.setUserVisible(false); + this->lastOpenDir.setUserVisible(false); + this->lastSelectedFormatFilter.setUserVisible(false); + + // Meshing + this->meshingQuality.mutableEnumeration().changeTrContext(AppModuleProperties::textIdContext()); + settings->addSetting(&this->meshingQuality, groupId_meshing); + settings->addSetting(&this->meshingChordalDeflection, groupId_meshing); + settings->addSetting(&this->meshingAngularDeflection, groupId_meshing); + settings->addSetting(&this->meshingRelative, groupId_meshing); + + // Graphics + settings->addSetting(&this->navigationStyle, groupId_graphics); + settings->addSetting(&this->defaultShowOriginTrihedron, groupId_graphics); + settings->addSetting(&this->instantZoomFactor, groupId_graphics); + // -- Clip planes + settings->addSetting(&this->clipPlanesCappingOn, sectionId_graphicsClipPlanes); + settings->addSetting(&this->clipPlanesCappingHatchOn, sectionId_graphicsClipPlanes); + // -- Mesh defaults + settings->addSetting(&this->meshDefaultsColor, sectionId_graphicsMeshDefaults); + settings->addSetting(&this->meshDefaultsEdgeColor, sectionId_graphicsMeshDefaults); + settings->addSetting(&this->meshDefaultsMaterial, sectionId_graphicsMeshDefaults); + settings->addSetting(&this->meshDefaultsShowEdges, sectionId_graphicsMeshDefaults); + settings->addSetting(&this->meshDefaultsShowNodes, sectionId_graphicsMeshDefaults); + + // Register reset functions + settings->addResetFunction(sectionId_systemUnits, [=]{ + this->unitSystemDecimals.setValue(2); + this->unitSystemSchema.setValue(UnitSystem::SI); + }); + settings->addResetFunction(groupId_application, [&]{ + this->language.setValue(AppModule::languages().findValueByName("en")); + this->recentFiles.setValue({}); + this->lastOpenDir.setValue({}); + this->lastSelectedFormatFilter.setValue({}); + this->linkWithDocumentSelector.setValue(true); + }); + settings->addResetFunction(groupId_graphics, [=]{ + this->navigationStyle.setValue(WidgetOccViewController::NavigationStyle::Mayo); + this->defaultShowOriginTrihedron.setValue(true); + this->instantZoomFactor.setValue(5.); + }); + settings->addResetFunction(groupId_meshing, [&]{ + this->meshingQuality.setValue(BRepMeshQuality::Normal); + this->meshingChordalDeflection.setQuantity(1 * Quantity_Millimeter); + this->meshingAngularDeflection.setQuantity(20 * Quantity_Degree); + this->meshingRelative.setValue(false); + }); + settings->addResetFunction(sectionId_graphicsClipPlanes, [=]{ + this->clipPlanesCappingOn.setValue(true); + this->clipPlanesCappingHatchOn.setValue(true); + }); + settings->addResetFunction(sectionId_graphicsMeshDefaults, [=]{ + const GraphicsMeshObjectDriver::DefaultValues meshDefaults; + this->meshDefaultsColor.setValue(meshDefaults.color); + this->meshDefaultsEdgeColor.setValue(meshDefaults.edgeColor); + this->meshDefaultsMaterial.setValue(meshDefaults.material); + this->meshDefaultsShowEdges.setValue(meshDefaults.showEdges); + this->meshDefaultsShowNodes.setValue(meshDefaults.showNodes); + }); +} + +void AppModuleProperties::IO_bindParameters(const IO::System* ioSystem) +{ + // Import + const auto groupId_Import = m_settings->addGroup(textId("import")); + for (IO::Format format : ioSystem->readerFormats()) { + auto sectionId_format = m_settings->addSection(groupId_Import, IO::formatIdentifier(format)); + const IO::FactoryReader* factory = ioSystem->findFactoryReader(format); + std::unique_ptr ptrGroup = factory->createProperties(format, m_settings); + if (ptrGroup) { + for (Property* property : ptrGroup->properties()) + m_settings->addSetting(property, sectionId_format); + + PropertyGroup* rawPtrGroup = ptrGroup.get(); + m_settings->addResetFunction(sectionId_format, [=]{ rawPtrGroup->restoreDefaults(); }); + m_mapFormatReaderParameters.insert({ format, rawPtrGroup }); + m_vecPtrPropertyGroup.push_back(std::move(ptrGroup)); + } + } + + // Export + const auto groupId_Export = m_settings->addGroup(textId("export")); + for (IO::Format format : ioSystem->writerFormats()) { + auto sectionId_format = m_settings->addSection(groupId_Export, IO::formatIdentifier(format)); + const IO::FactoryWriter* factory = ioSystem->findFactoryWriter(format); + std::unique_ptr ptrGroup = factory->createProperties(format, m_settings); + if (ptrGroup) { + for (Property* property : ptrGroup->properties()) + m_settings->addSetting(property, sectionId_format); + + PropertyGroup* rawPtrGroup = ptrGroup.get(); + m_settings->addResetFunction(sectionId_format, [=]{ rawPtrGroup->restoreDefaults(); }); + m_mapFormatWriterParameters.insert({ format, ptrGroup.get() }); + m_vecPtrPropertyGroup.push_back(std::move(ptrGroup)); + } + } +} + +void AppModuleProperties::retranslate() +{ + this->language.setDescription( + textIdTr("Language used for the application. Change will take effect after application restart")); + this->linkWithDocumentSelector.setDescription( + textIdTr("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")); + this->meshingQuality.setDescription( + textIdTr("Controls precision of the mesh to be computed from the BRep shape")); + this->meshingChordalDeflection.setDescription( + textIdTr("For the tesselation of faces the chordal deflection limits the distance between " + "a curve and its tessellation")); + this->meshingAngularDeflection.setDescription( + textIdTr("For the tesselation of faces the angular deflection limits the angle between " + "subsequent segments in a polyline")); + this->meshingRelative.setDescription( + textIdTr("Relative computation of edge tolerance\n\n" + "If activated, deflection used for the polygonalisation of each edge will be " + "`ChordalDeflection` × `SizeOfEdge`. The deflection used for the faces will be " + "the maximum deflection of their edges.")); + this->navigationStyle.setDescription( + textIdTr("3D view manipulation shortcuts configuration to mimic other common CAD applications")); + this->defaultShowOriginTrihedron.setDescription( + textIdTr("Show or hide by default the trihedron centered at world origin. " + "This doesn't affect 3D view of currently opened documents")); + this->clipPlanesCappingOn.setDescription( + textIdTr("Enable capping of currently clipped graphics")); + this->clipPlanesCappingHatchOn.setDescription( + textIdTr("Enable capping hatch texture of currently clipped graphics")); +} + +void AppModuleProperties::onPropertyChanged(Property* prop) +{ + if (prop == &this->meshDefaultsColor + || prop == &this->meshDefaultsEdgeColor + || prop == &this->meshDefaultsMaterial + || prop == &this->meshDefaultsShowEdges + || prop == &this->meshDefaultsShowNodes) + { + auto values = GraphicsMeshObjectDriver::defaultValues(); + values.color = this->meshDefaultsColor.value(); + values.edgeColor = this->meshDefaultsEdgeColor.value(); + values.material = static_cast(this->meshDefaultsMaterial.value()); + values.showEdges = this->meshDefaultsShowEdges.value(); + values.showNodes = this->meshDefaultsShowNodes.value(); + GraphicsMeshObjectDriver::setDefaultValues(values); + } + else if (prop == &this->meshingQuality) { + const bool isUserDefined = this->meshingQuality.value() == BRepMeshQuality::UserDefined; + this->meshingChordalDeflection.setEnabled(isUserDefined); + this->meshingAngularDeflection.setEnabled(isUserDefined); + this->meshingRelative.setEnabled(isUserDefined); + } + + PropertyGroup::onPropertyChanged(prop); +} + +} // namespace Mayo diff --git a/src/app/app_module_properties.h b/src/app/app_module_properties.h new file mode 100644 index 00000000..267632a3 --- /dev/null +++ b/src/app/app_module_properties.h @@ -0,0 +1,87 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "recent_files.h" + +#include "../base/io_format.h" +#include "../base/occt_enums.h" +#include "../base/property.h" +#include "../base/property_builtins.h" +#include "../base/property_enumeration.h" +#include "../base/settings.h" +#include "../base/unit_system.h" +#include "widget_occ_view_controller.h" + +#include +#include +#include + +namespace Mayo { + +namespace IO { class System; } +class Settings; + +// Provides a container of all the application properties(settings) +// Properties are structured into predefined Settings groups/sections +class AppModuleProperties : public PropertyGroup { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::AppModuleProperties) +public: + // Create properties, the PropertyGroup will be a child of group `settings` + // Any value/enabled change will be reported to the Settings object to emit signals + AppModuleProperties(Settings* settings); + + // Iterates over reader/writer factories and bind properties + void IO_bindParameters(const IO::System* ioSystem); + + // Re-initialize translatable descriptions assigned to properties + void retranslate(); + + // System + const Settings::GroupIndex groupId_system; + PropertyInt unitSystemDecimals{ this, textId("decimalCount") }; + PropertyEnum unitSystemSchema{ this, textId("schema") }; + // Application + const Settings::GroupIndex groupId_application; + PropertyEnumeration language; + PropertyRecentFiles recentFiles{ this, textId("recentFiles") }; + PropertyFilePath lastOpenDir{ this, textId("lastOpenFolder") }; + PropertyString lastSelectedFormatFilter{ this, textId("lastSelectedFormatFilter") }; + PropertyBool linkWithDocumentSelector{ this, textId("linkWithDocumentSelector") }; + // Meshing + enum class BRepMeshQuality { VeryCoarse, Coarse, Normal, Precise, VeryPrecise, UserDefined }; + PropertyEnum meshingQuality{ this, textId("meshingQuality") }; + PropertyLength meshingChordalDeflection{ this, textId("meshingChordalDeflection") }; + PropertyAngle meshingAngularDeflection{ this, textId("meshingAngularDeflection") }; + PropertyBool meshingRelative{ this, textId("meshingRelative") }; + // Graphics + PropertyEnum navigationStyle{ this, textId("navigationStyle") }; + PropertyBool defaultShowOriginTrihedron{ this, textId("defaultShowOriginTrihedron") }; + PropertyDouble instantZoomFactor{ this, textId("instantZoomFactor") }; + // -- Graphics/ClipPlanes + PropertyBool clipPlanesCappingOn{ this, textId("cappingOn") }; + PropertyBool clipPlanesCappingHatchOn{ this, textId("cappingHatchOn") }; + // -- Graphics/MeshDefaults + PropertyOccColor meshDefaultsColor{ this, textId("color") }; + PropertyOccColor meshDefaultsEdgeColor{ this, textId("edgeColor") }; + PropertyEnumeration meshDefaultsMaterial{ this, textId("material"), &OcctEnums::Graphic3d_NameOfMaterial() }; + PropertyBool meshDefaultsShowEdges{ this, textId("showEgesOn") }; + PropertyBool meshDefaultsShowNodes{ this, textId("showNodesOn") }; + +protected: + // -- from PropertyGroup + void onPropertyChanged(Property* prop) override; + +private: + friend class AppModule; + Settings* m_settings = nullptr; + std::vector> m_vecPtrPropertyGroup; + std::unordered_map m_mapFormatReaderParameters; + std::unordered_map m_mapFormatWriterParameters; +}; + +} // namespace Mayo diff --git a/src/app/button_flat.cpp b/src/app/button_flat.cpp index c5884b6b..cf645cd9 100644 --- a/src/app/button_flat.cpp +++ b/src/app/button_flat.cpp @@ -11,8 +11,7 @@ #include #include #include -#include - +#include // WARNING Qt5 / Qt6 #include namespace Mayo { @@ -142,12 +141,13 @@ void ButtonFlat::paintEvent(QPaintEvent*) (surface.width() - m_iconSize.width()) / 2, (surface.height() - m_iconSize.height()) / 2, m_iconSize.width(), - m_iconSize.height()); + m_iconSize.height() + ); const QIcon::Mode iconMode = isEnabled ? QIcon::Normal : QIcon::Disabled; m_icon.paint(&painter, iconRect, Qt::AlignCenter, iconMode); } -void ButtonFlat::enterEvent(QEvent* event) +void ButtonFlat::enterEvent(QWidgetEnterEvent* event) { m_isMouseHover = true; this->update(); diff --git a/src/app/button_flat.h b/src/app/button_flat.h index be5e826a..a740a7c9 100644 --- a/src/app/button_flat.h +++ b/src/app/button_flat.h @@ -6,6 +6,8 @@ #pragma once +#include "widgets_utils.h" + #include #include #include @@ -52,7 +54,7 @@ class ButtonFlat : public QWidget { protected: void paintEvent(QPaintEvent* event) override; - void enterEvent(QEvent* event) override; + void enterEvent(QWidgetEnterEvent* event) override; void leaveEvent(QEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; diff --git a/src/app/cli_export.cpp b/src/app/cli_export.cpp new file mode 100644 index 00000000..3134baf4 --- /dev/null +++ b/src/app/cli_export.cpp @@ -0,0 +1,256 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "cli_export.h" + +#include "app_module.h" +#include "console.h" +#include "qstring_conv.h" +#include "../base/application.h" +#include "../base/io_system.h" +#include "../base/messenger.h" +#include "../base/task_manager.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace Mayo { + +class CliExport { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::CliExport) +}; + +namespace { + +// Provides thread-safe status of a task +struct TaskStatus { + std::atomic finished = {}; + std::atomic success = {}; +}; + +// Provides helper data that exists during execution of cli_asyncExportDocuments() function +struct Helper : public QObject { + // Task manager object to be used + TaskManager taskMgr; + // Counter decremented for each finished export task, when 0 is reached then quit + std::atomic exportTaskCount = {}; + // Mapping between a task id and the task status + std::unordered_map> mapTaskStatus; + // Mapping between a task id and corresponding width of the progress line in console + std::unordered_map mapTaskLineWidth; + // Count of progress lines in console after last call to printTaskProgress() + int lastPrintProgressLineCount = 0; +}; + +// Collects emitted error messages into a single string object +class ErrorMessageCollect : public Messenger { +public: + void emitMessage(MessageType msgType, std::string_view text) override + { + if (msgType == MessageType::Error) { + m_message += text; + m_message += " "; + } + } + + const std::string& message() const { return m_message; } + +private: + std::string m_message; +}; + +void printTaskProgress(Helper* helper, TaskId taskId) +{ + std::string strMessage = helper->taskMgr.title(taskId); + std::replace(strMessage.begin(), strMessage.end(), '\n', ' '); + strMessage = consoleToPrintable(strMessage); + auto lineWidth = int(strMessage.size()); + const bool taskFinished = helper->mapTaskStatus.at(taskId)->finished; + const bool taskSuccess = helper->mapTaskStatus.at(taskId)->success; + if (taskFinished && !taskSuccess) { + consoleSetTextColor(ConsoleColor::Red); + std::cout << strMessage; + consoleSetTextColor(ConsoleColor::Default); + } + else { + const int progress = helper->taskMgr.progress(taskId); + if (progress >= 100) + consoleSetTextColor(ConsoleColor::Green); + + std::cout << std::setfill(' ') << std::right << std::setw(3) << progress << "% "; + std::cout << strMessage; + lineWidth += 5; + if (progress >= 100) + consoleSetTextColor(ConsoleColor::Default); + } + + const int printWidth = consoleWidth(); + auto itLineFound = helper->mapTaskLineWidth.find(taskId); + const int lineWidthOld = itLineFound != helper->mapTaskLineWidth.cend() ? itLineFound->second : printWidth - 1; + for (int i = 0; i < (lineWidthOld - lineWidth); ++i) + std::cout << ' '; + + helper->mapTaskLineWidth.insert_or_assign(taskId, lineWidth); + helper->lastPrintProgressLineCount += printWidth > 0 ? (lineWidth / printWidth) + 1 : 1; + std::cout << "\n"; +} + +bool importInDocument(DocumentPtr doc, const CliExportArgs& args, Helper* helper, TaskProgress* progress) +{ + auto appModule = AppModule::get(); + + // If export operation targets some mesh format then force meshing of imported BRep shapes + bool brepMeshRequired = false; + for (const FilePath& filepath : args.filesToExport) { + const IO::Format format = appModule->ioSystem()->probeFormat(filepath); + brepMeshRequired = IO::formatProvidesMesh(format); + if (brepMeshRequired) + break; // Interrupt + } + + ErrorMessageCollect errorCollect; + const bool okImport = appModule->ioSystem()->importInDocument() + .targetDocument(doc) + .withFilepaths(args.filesToOpen) + .withParametersProvider(appModule) + .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { + appModule->computeBRepMesh(labelEntity, progress); + }) + .withEntityPostProcessRequiredIf([=](IO::Format){ return brepMeshRequired; }) + .withEntityPostProcessInfoProgress(20, CliExport::textIdTr("Mesh BRep shapes")) + .withMessenger(&errorCollect) + .withTaskProgress(progress) + .execute(); + helper->taskMgr.setTitle(progress->taskId(), okImport ? CliExport::textIdTr("Imported") : errorCollect.message()); + helper->mapTaskStatus.at(progress->taskId())->success = okImport; + helper->mapTaskStatus.at(progress->taskId())->finished = true; + return okImport; +} + +void exportDocument(const DocumentPtr& doc, const FilePath& filepath, Helper* helper, TaskProgress* progress) +{ + auto appModule = AppModule::get(); + ErrorMessageCollect errorCollect; + const IO::Format format = appModule->ioSystem()->probeFormat(filepath); + const ApplicationItem appItems[] = { doc }; + const bool okExport = appModule->ioSystem()->exportApplicationItems() + .targetFile(filepath) + .targetFormat(format) + .withItems(appItems) + .withParameters(appModule->findWriterParameters(format)) + .withMessenger(&errorCollect) + .withTaskProgress(progress) + .execute(); + const std::string strFilename = filepath.filename().u8string(); + const std::string msg = + okExport ? + fmt::format(CliExport::textIdTr("Exported {}"), strFilename) : + errorCollect.message(); + helper->taskMgr.setTitle(progress->taskId(), msg); + helper->mapTaskStatus.at(progress->taskId())->success = okExport; + helper->mapTaskStatus.at(progress->taskId())->finished = true; + --(helper->exportTaskCount); +} + +} // namespace + +void cli_asyncExportDocuments( + Application* app, const CliExportArgs& args, std::function fnContinuation) +{ + auto helper = new Helper; // Allocated on heap because current function is asynchronous + auto taskMgr = &helper->taskMgr; + + // Helper function to exit current function + auto fnExit = [=](int retCode) { + helper->deleteLater(); + fnContinuation(retCode); + }; + + // Helper function to print in console the progress info of current function + auto fnPrintProgress = [=]{ + consoleCursorMoveUp(helper->lastPrintProgressLineCount); + helper->lastPrintProgressLineCount = 0; + std::cout << "\r"; + taskMgr->foreachTask([=](TaskId taskId) { printTaskProgress(helper, taskId); }); + std::cout.flush(); + }; + + // Show progress/traces corresponding to task events + QObject::connect(taskMgr, &TaskManager::started, app, [=](TaskId taskId) { + if (args.progressReport) + fnPrintProgress(); + else + qInfo() << to_QString(taskMgr->title(taskId)); + }); + QObject::connect(taskMgr, &TaskManager::ended, app, [=](TaskId taskId) { + if (args.progressReport) { + fnPrintProgress(); + } + else { + if (helper->mapTaskStatus.at(taskId)->success) + qInfo() << to_QString(taskMgr->title(taskId)); + else + qCritical() << to_QString(taskMgr->title(taskId)); + } + }); + QObject::connect(taskMgr, &TaskManager::progressChanged, app, [=]{ + if (args.progressReport) + fnPrintProgress(); + }); + + helper->exportTaskCount = int(args.filesToExport.size()); + QObject::connect(taskMgr, &TaskManager::ended, app, [=]{ + if (helper->exportTaskCount == 0) { + bool okExport = true; + for (const auto& mapPair : helper->mapTaskStatus) { + const TaskStatus* status = mapPair.second.get(); + okExport = okExport && status->success; + } + + fnExit(okExport ? EXIT_SUCCESS : EXIT_FAILURE); + } + }); + + // Suppress output from OpenCascade + Message::DefaultMessenger()->RemovePrinters(Message_Printer::get_type_descriptor()); + + // Execute import operation(synchronous) + DocumentPtr doc = app->newDocument(); + bool okImport = true; + const TaskId importTaskId = taskMgr->newTask([&](TaskProgress* progress) { + okImport = importInDocument(doc, args, helper, progress); + }); + helper->mapTaskStatus.insert({ importTaskId, std::make_unique() }); + taskMgr->setTitle(importTaskId, CliExport::textIdTr("Importing...")); + taskMgr->exec(importTaskId, TaskAutoDestroy::Off); + if (!okImport) + return fnExit(EXIT_FAILURE); // Error + + // Run export operations(asynchronous) + for (const FilePath& filepath : args.filesToExport) { + const TaskId taskId = taskMgr->newTask([=](TaskProgress* progress) { + exportDocument(doc, filepath, helper, progress); + }); + const std::string strFilename = filepath.filename().u8string(); + helper->mapTaskStatus.insert({ taskId, std::make_unique() }); + taskMgr->setTitle(taskId, fmt::format(CliExport::textIdTr("Exporting {}..."), strFilename)); + } + + taskMgr->foreachTask([=](TaskId taskId) { + if (taskId != importTaskId) + taskMgr->run(taskId, TaskAutoDestroy::Off); + }); +} + +} // namespace Mayo diff --git a/src/app/cli_export.h b/src/app/cli_export.h new file mode 100644 index 00000000..e57944b6 --- /dev/null +++ b/src/app/cli_export.h @@ -0,0 +1,33 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/filepath.h" +#include "../base/span.h" + +#include + +namespace Mayo { + +class Application; + +// Contains arguments for the cli_asyncExportDocuments() function +struct CliExportArgs { + bool progressReport = true; + Span filesToOpen; + Span filesToExport; +}; + +// Asynchronously exports input file(s) listed in 'args' +// Calls 'fnContinuation' at the end of execution +void cli_asyncExportDocuments( + Application* app, + const CliExportArgs& args, + std::function fnContinuation +); + +} // namespace Mayo diff --git a/src/app/console.cpp b/src/app/console.cpp index 2f4419d4..cc70ce10 100644 --- a/src/app/console.cpp +++ b/src/app/console.cpp @@ -6,10 +6,7 @@ #include "console.h" -#include "../base/global.h" - #include - #ifdef Q_OS_WIN # include # include @@ -19,6 +16,9 @@ # include #endif +#include "../base/global.h" +#include "qstring_conv.h" + namespace Mayo { void consoleSetTextColor(ConsoleColor color) @@ -27,12 +27,12 @@ void consoleSetTextColor(ConsoleColor color) #ifdef Q_OS_WIN auto fnColorFlags = [](ConsoleColor color) { switch (color) { - case ConsoleColor::Black: return 0; - case ConsoleColor::Red: return FOREGROUND_RED; - case ConsoleColor::Green: return FOREGROUND_GREEN; - case ConsoleColor::Blue: return FOREGROUND_BLUE; - case ConsoleColor::Yellow: return FOREGROUND_RED | FOREGROUND_GREEN; - case ConsoleColor::Cyan: return FOREGROUND_GREEN | FOREGROUND_BLUE; + case ConsoleColor::Black: return 0; + case ConsoleColor::Red: return FOREGROUND_RED; + case ConsoleColor::Green: return FOREGROUND_GREEN; + case ConsoleColor::Blue: return FOREGROUND_BLUE; + case ConsoleColor::Yellow: return FOREGROUND_RED | FOREGROUND_GREEN; + case ConsoleColor::Cyan: return FOREGROUND_GREEN | FOREGROUND_BLUE; case ConsoleColor::Magenta: return FOREGROUND_RED | FOREGROUND_BLUE; case ConsoleColor::Default: case ConsoleColor::White: @@ -56,14 +56,14 @@ void consoleSetTextColor(ConsoleColor color) #else auto fnCode = [](ConsoleColor color, bool isBrightText) { switch (color) { - case ConsoleColor::Black: return isBrightText ? "\e[30;1m" : "\e[30m"; - case ConsoleColor::Red: return isBrightText ? "\e[31;1m" : "\e[31m"; - case ConsoleColor::Green: return isBrightText ? "\e[32;1m" : "\e[32m"; - case ConsoleColor::Yellow: return isBrightText ? "\e[33;1m" : "\e[33m"; - case ConsoleColor::Blue: return isBrightText ? "\e[34;1m" : "\e[34m"; + case ConsoleColor::Black: return isBrightText ? "\e[30;1m" : "\e[30m"; + case ConsoleColor::Red: return isBrightText ? "\e[31;1m" : "\e[31m"; + case ConsoleColor::Green: return isBrightText ? "\e[32;1m" : "\e[32m"; + case ConsoleColor::Yellow: return isBrightText ? "\e[33;1m" : "\e[33m"; + case ConsoleColor::Blue: return isBrightText ? "\e[34;1m" : "\e[34m"; case ConsoleColor::Magenta: return isBrightText ? "\e[35;1m" : "\e[35m"; - case ConsoleColor::Cyan: return isBrightText ? "\e[36;1m" : "\e[36m"; - case ConsoleColor::White: return isBrightText ? "\e[37;1m" : "\e[37m"; + case ConsoleColor::Cyan: return isBrightText ? "\e[36;1m" : "\e[36m"; + case ConsoleColor::White: return isBrightText ? "\e[37;1m" : "\e[37m"; case ConsoleColor::Default: default: return "\e[0m"; } @@ -155,7 +155,7 @@ std::string consoleToPrintable(const QString& str) std::string consoleToPrintable(std::string_view str) { #ifdef Q_OS_WIN - return consoleToPrintable(QString::fromUtf8(str.data(), str.size())); + return consoleToPrintable(to_QString(str)); #else return std::string(str); // utf8 #endif diff --git a/src/app/dialog_about.cpp b/src/app/dialog_about.cpp index a68a7598..4c11e362 100644 --- a/src/app/dialog_about.cpp +++ b/src/app/dialog_about.cpp @@ -15,19 +15,17 @@ namespace Mayo { -DialogAbout::DialogAbout(QWidget *parent) +DialogAbout::DialogAbout(QWidget* parent) : QDialog(parent), m_ui(new Ui_DialogAbout) { m_ui->setupUi(this); m_ui->label_AppByOrg->setText( - tr("%1 By %2").arg(QApplication::applicationName(), - QApplication::organizationName())); + 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)); - m_ui->label_BuildDateTime->setText( - m_ui->label_BuildDateTime->text().arg(__DATE__).arg(__TIME__)); + m_ui->label_Version->setText(m_ui->label_Version->text().arg(strVersion).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 diff --git a/src/app/dialog_about.h b/src/app/dialog_about.h index 6af6f803..34bb8114 100644 --- a/src/app/dialog_about.h +++ b/src/app/dialog_about.h @@ -13,7 +13,7 @@ namespace Mayo { class DialogAbout : public QDialog { Q_OBJECT public: - DialogAbout(QWidget *parent = nullptr); + DialogAbout(QWidget* parent = nullptr); ~DialogAbout(); private: diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index c6e36176..015aa886 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -67,7 +67,7 @@ enum TreeWidgetItemRole { static QStringUtils::TextOptions appDefaultTextOptions() { - return AppModule::get(Application::instance())->defaultTextOptions(); + return AppModule::get()->defaultTextOptions(); } static void loadLabelAttributes(const TDF_Label& label, QTreeWidgetItem* treeItem) diff --git a/src/app/dialog_task_manager.cpp b/src/app/dialog_task_manager.cpp index f16cfb7e..9ae45309 100644 --- a/src/app/dialog_task_manager.cpp +++ b/src/app/dialog_task_manager.cpp @@ -185,7 +185,7 @@ void DialogTaskManager::onTaskProgressStep(TaskId taskId, std::string_view name) const QString taskTitle = to_QString(m_taskMgr->title(taskId)); QString text = taskTitle; if (!name.empty()) { - const QLocale& locale = AppModule::get(Application::instance())->locale(); + const QLocale& locale = AppModule::get()->locale(); if (!text.isEmpty()) QStringUtils::append(&text, tr(" / "), locale); diff --git a/src/app/document_property_group.cpp b/src/app/document_property_group.cpp new file mode 100644 index 00000000..3ed202c3 --- /dev/null +++ b/src/app/document_property_group.cpp @@ -0,0 +1,43 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "document_property_group.h" + +#include "app_module.h" +#include "filepath_conv.h" +#include "qstring_conv.h" +#include "qstring_utils.h" +#include "../base/application.h" + +#include +#include + +namespace Mayo { + +DocumentPropertyGroup::DocumentPropertyGroup(const DocumentPtr& doc) +{ + this->filePath.setValue(filepathCanonical(doc->filePath())); + + const auto fileSize = filepathFileSize(doc->filePath()); + auto appModule = AppModule::get(); + const QString strFileSize = QStringUtils::bytesText(fileSize, appModule->locale()); + this->strFileSize.setValue(to_stdString(strFileSize)); + + const QFileInfo fileInfo = filepathTo(doc->filePath()); + const QString strCreated = appModule->locale().toString(fileInfo.birthTime(), QLocale::ShortFormat); + const QString strModified = appModule->locale().toString(fileInfo.lastModified(), QLocale::ShortFormat); + this->strCreatedDateTime.setValue(to_stdString(strCreated)); + this->strModifiedDateTime.setValue(to_stdString(strModified)); + + this->strOwner.setValue(to_stdString(fileInfo.owner())); + + this->entityCount.setValue(doc->entityCount()); + + for (Property* prop : this->properties()) + prop->setUserReadOnly(true); +} + +} // namespace Mayo diff --git a/src/app/document_property_group.h b/src/app/document_property_group.h new file mode 100644 index 00000000..6d8cfdec --- /dev/null +++ b/src/app/document_property_group.h @@ -0,0 +1,27 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/property_builtins.h" +#include "../base/document.h" + +namespace Mayo { + +class DocumentPropertyGroup : public PropertyGroup { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::DocumentPropertyGroup) +public: + DocumentPropertyGroup(const DocumentPtr& doc); + + PropertyFilePath filePath{ this, textId("filepath") }; + PropertyString strFileSize{ this, textId("fileSize") }; + PropertyString strCreatedDateTime{ this, textId("createdDateTime") }; + PropertyString strModifiedDateTime{ this, textId("modifiedDateTime") }; + PropertyString strOwner{ this, textId("owner") }; + PropertyInt entityCount{ this, textId("entityCount") }; +}; + +} // namespace Mayo diff --git a/src/app/document_tree_node_properties_providers.cpp b/src/app/document_tree_node_properties_providers.cpp index bea89a53..4a277d45 100644 --- a/src/app/document_tree_node_properties_providers.cpp +++ b/src/app/document_tree_node_properties_providers.cpp @@ -7,6 +7,7 @@ #include "document_tree_node_properties_providers.h" #include "../base/caf_utils.h" +#include "../base/data_triangulation.h" #include "../base/document.h" #include "../base/document_tree_node.h" #include "../base/mesh_utils.h" @@ -15,7 +16,6 @@ #include "qstring_conv.h" #include -#include namespace Mayo { @@ -211,14 +211,14 @@ class Mesh_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroup public: Properties(const DocumentTreeNode& treeNode) { - auto attrTriangulation = CafUtils::findAttribute(treeNode.label()); + auto attrTriangulation = CafUtils::findAttribute(treeNode.label()); Handle_Poly_Triangulation polyTri; if (!attrTriangulation.IsNull()) polyTri = attrTriangulation->Get(); m_propertyNodeCount.setValue(!polyTri.IsNull() ? polyTri->NbNodes() : 0); m_propertyTriangleCount.setValue(!polyTri.IsNull() ? polyTri->NbTriangles() : 0); - m_propertyArea.setQuantity(MeshUtils::triangulationArea(polyTri) * Quantity_SquaredMillimeter); + m_propertyArea.setQuantity(MeshUtils::triangulationArea(polyTri) * Quantity_SquareMillimeter); m_propertyVolume.setQuantity(MeshUtils::triangulationVolume(polyTri) * Quantity_CubicMillimeter); for (Property* property : this->properties()) property->setUserReadOnly(true); @@ -232,7 +232,7 @@ class Mesh_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroup bool Mesh_DocumentTreeNodePropertiesProvider::supports(const DocumentTreeNode& treeNode) const { - return CafUtils::hasAttribute(treeNode.label()); + return CafUtils::hasAttribute(treeNode.label()); } std::unique_ptr diff --git a/src/app/main.cpp b/src/app/main.cpp index d42dee47..a6da0314 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -7,17 +7,19 @@ #include "../base/application.h" #include "../base/document_tree_node_properties_provider.h" #include "../base/io_system.h" -#include "../base/messenger.h" #include "../base/settings.h" -#include "../base/task_manager.h" #include "../io_dxf/io_dxf.h" #include "../io_gmio/io_gmio.h" #include "../io_image/io_image.h" #include "../io_occ/io_occ.h" -#include "../graphics/graphics_object_driver.h" +#include "../io_ply/io_ply_reader.h" +#include "../io_ply/io_ply_writer.h" +#include "../graphics/graphics_mesh_object_driver.h" +#include "../graphics/graphics_shape_object_driver.h" #include "../gui/gui_application.h" #include "../gui/qtgui_utils.h" #include "app_module.h" +#include "cli_export.h" #include "console.h" #include "document_tree_node_properties_providers.h" #include "filepath_conv.h" @@ -44,13 +46,10 @@ #include #include -#include - #include #include #include #include -#include #include #include @@ -152,7 +151,7 @@ static CommandLineArguments processCommandLine() // Configure command-line parser QCommandLineParser cmdParser; cmdParser.setApplicationDescription( - Main::tr("Mayo, an open-source 3D viewer based on Qt5/OpenCascade")); + Main::tr("Mayo the opensource 3D CAD viewer and converter")); cmdParser.addHelpOption(); cmdParser.addVersionOption(); @@ -196,6 +195,13 @@ static CommandLineArguments processCommandLine() Main::tr("Files to open at startup, optionally"), Main::tr("[files...]")); +#ifdef MAYO_WITH_TESTS + const QCommandLineOption cmdRunTests( + QStringList{ "runtests" }, + Main::tr("Execute unit tests and exit application")); + cmdParser.addOption(cmdRunTests); +#endif + cmdParser.process(QCoreApplication::arguments()); // Retrieve arguments @@ -275,52 +281,22 @@ static void initOpenCascadeEnvironment(const FilePath& settingsFilepath) } } -// Initializes "Base" objects -static void initBase(QCoreApplication* qtApp) +static std::string_view qtTranslate(const TextId& text, int n) { - auto app = Application::instance(); - app->settings()->setStorage(std::make_unique()); - - // Load translation files + const QString qstr = QCoreApplication::translate(text.trContext.data(), text.key.data(), nullptr, n); + auto qstrHash = qHash(qstr); + static std::unordered_map mapStr; + static QReadWriteLock mapStrLock; { - const QString qmFilePath = AppModule::qmFilePath(AppModule::languageCode(app)); - auto translator = new QTranslator(app.get()); - if (translator->load(qmFilePath)) - qtApp->installTranslator(translator); - else - qWarning() << Main::tr("Failed to load translation file [path=%1]").arg(qmFilePath); + QReadLocker locker(&mapStrLock); + auto it = mapStr.find(qstrHash); + if (it != mapStr.cend()) + return it->second; } - // Set Qt i18n backend - app->addTranslator([=](const TextId& text, int n) -> std::string_view { - const QString qstr = qtApp->translate(text.trContext.data(), text.key.data(), nullptr, n); - auto qstrHash = qHash(qstr); - static std::unordered_map mapStr; - static QReadWriteLock mapStrLock; - { - QReadLocker locker(&mapStrLock); - auto it = mapStr.find(qstrHash); - if (it != mapStr.cend()) - return it->second; - } - - QWriteLocker locker(&mapStrLock); - auto [it, ok] = mapStr.insert({ qstrHash, to_stdString(qstr) }); - return ok ? it->second : std::string_view{}; - }); - - // Register I/O objects - app->ioSystem()->addFactoryReader(std::make_unique()); - app->ioSystem()->addFactoryReader(std::make_unique()); - app->ioSystem()->addFactoryWriter(std::make_unique()); - app->ioSystem()->addFactoryWriter(IO::GmioFactoryWriter::create()); - IO::addPredefinedFormatProbes(app->ioSystem()); - - // Register providers to query document tree node properties - app->documentTreeNodePropertiesProviderTable()->addProvider( - std::make_unique()); - app->documentTreeNodePropertiesProviderTable()->addProvider( - std::make_unique()); + QWriteLocker locker(&mapStrLock); + auto [it, ok] = mapStr.insert({ qstrHash, to_stdString(qstr) }); + return ok ? it->second : std::string_view{}; } // Helper to query the OpenGL version string @@ -371,7 +347,7 @@ static void initGui(GuiApplication* guiApp) IWidgetOccView::setCreator(&QWidgetOccView::create); // Use QOpenGLWidget if possible -#if OCC_VERSION_HEX >= 0x070600 +#if OCC_VERSION_HEX >= 0x070600 && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (qobject_cast(QCoreApplication::instance())) { // QOpenGL requires QGuiApplication const std::string strGlVersion = queryGlVersionString(); const QVersionNumber glVersion = parseSemanticVersionString(strGlVersion); @@ -386,214 +362,11 @@ static void initGui(GuiApplication* guiApp) } #endif - // Register I/O objects - guiApp->application()->ioSystem()->addFactoryWriter(std::make_unique(guiApp)); - - // Register Graphics/TreeNode mapping drivers - guiApp->graphicsTreeNodeMappingDriverTable()->addDriver( - std::make_unique()); - // Register Graphics entity drivers - guiApp->graphicsObjectDriverTable()->addDriver(std::make_unique()); - guiApp->graphicsObjectDriverTable()->addDriver(std::make_unique()); + guiApp->addGraphicsObjectDriver(std::make_unique()); + guiApp->addGraphicsObjectDriver(std::make_unique()); } -// Asynchronously exports input file(s) listed in 'args' -// Calls 'fnContinuation' at the end of execution -static void cli_asyncExportDocuments( - Application* app, const CommandLineArguments& args, std::function fnContinuation) -{ - struct TaskStatus { - std::atomic finished = {}; - std::atomic success = {}; - }; - - struct Helper : public QObject { - // Task manager object dedicated to the scope of current function - TaskManager taskMgr; - // Counter decremented for each export task finished, when 0 is reached then quit - std::atomic exportTaskCount = {}; - // Mapping between a task id and the task status - std::unordered_map> mapTaskStatus; - // Mapping between a task id and corresponding width of the progress line in console - std::unordered_map mapTaskLineWidth; - // Count of progress lines in console after last call to fnPrintProgress() - int lastPrintProgressLineCount = 0; - }; - - // Collects emitted error messages into a single string object - class ErrorMessageCollect : public Messenger { - public: - void emitMessage(MessageType msgType, std::string_view text) override { - if (msgType == MessageType::Error) - m_message += to_QString(text) + " "; - } - - std::string message() const { return to_stdString(m_message); } - - private: - QString m_message; - }; - - auto helper = new Helper; // Allocated on heap because current function is asynchronous - auto taskMgr = &helper->taskMgr; - auto appModule = AppModule::get(app); - - // Helper function to exit current function - auto fnExit = [=](int retCode) { - helper->deleteLater(); - fnContinuation(retCode); - }; - // Helper function to print in console the progress info of current function - auto fnPrintProgress = [=]{ - consoleCursorMoveUp(helper->lastPrintProgressLineCount); - helper->lastPrintProgressLineCount = 0; - std::cout << "\r"; - taskMgr->foreachTask([=](TaskId taskId) { - std::string strMessage = taskMgr->title(taskId); - std::replace(strMessage.begin(), strMessage.end(), '\n', ' '); - strMessage = consoleToPrintable(strMessage); - auto lineWidth = int(strMessage.size()); - const bool taskFinished = helper->mapTaskStatus.at(taskId)->finished; - const bool taskSuccess = helper->mapTaskStatus.at(taskId)->success; - if (taskFinished && !taskSuccess) { - consoleSetTextColor(ConsoleColor::Red); - std::cout << strMessage; - consoleSetTextColor(ConsoleColor::Default); - } - else { - const int progress = taskMgr->progress(taskId); - if (progress >= 100) - consoleSetTextColor(ConsoleColor::Green); - - std::cout << std::setfill(' ') << std::right << std::setw(3) << progress << "% "; - std::cout << strMessage; - lineWidth += 5; - if (progress >= 100) - consoleSetTextColor(ConsoleColor::Default); - } - - const int printWidth = consoleWidth(); - auto itLineFound = helper->mapTaskLineWidth.find(taskId); - const int lineWidthOld = itLineFound != helper->mapTaskLineWidth.cend() ? itLineFound->second : printWidth - 1; - for (int i = 0; i < (lineWidthOld - lineWidth); ++i) - std::cout << ' '; - - helper->mapTaskLineWidth.insert_or_assign(taskId, lineWidth); - helper->lastPrintProgressLineCount += printWidth > 0 ? (lineWidth / printWidth) + 1 : 1; - std::cout << "\n"; - }); - std::cout.flush(); - }; - - // Show progress/traces corresponding to task events - QObject::connect(taskMgr, &TaskManager::started, app, [=](TaskId taskId) { - if (args.cliProgressReport) - fnPrintProgress(); - else - qInfo() << to_QString(taskMgr->title(taskId)); - }); - QObject::connect(taskMgr, &TaskManager::ended, app, [=](TaskId taskId) { - if (args.cliProgressReport) { - fnPrintProgress(); - } - else { - if (helper->mapTaskStatus.at(taskId)->success) - qInfo() << to_QString(taskMgr->title(taskId)); - else - qCritical() << to_QString(taskMgr->title(taskId)); - } - }); - QObject::connect(taskMgr, &TaskManager::progressChanged, app, [=]{ - if (args.cliProgressReport) - fnPrintProgress(); - }); - - helper->exportTaskCount = int(args.listFilepathToExport.size()); - QObject::connect(taskMgr, &TaskManager::ended, app, [=]{ - if (helper->exportTaskCount == 0) { - bool okExport = true; - for (const auto& mapPair : helper->mapTaskStatus) { - const TaskStatus* status = mapPair.second.get(); - okExport = okExport && status->success; - } - - fnExit(okExport ? EXIT_SUCCESS : EXIT_FAILURE); - } - }); - - // If export operation targets some mesh format then force meshing of imported BRep shapes - bool brepMeshRequired = false; - for (const FilePath& filepath : args.listFilepathToExport) { - const IO::Format format = app->ioSystem()->probeFormat(filepath); - brepMeshRequired = IO::formatProvidesMesh(format); - if (brepMeshRequired) - break; // Interrupt - } - - // Suppress output from OpenCascade - Message::DefaultMessenger()->RemovePrinters(Message_Printer::get_type_descriptor()); - - // Execute import operation(synchronous) - DocumentPtr doc = app->newDocument(); - bool okImport = true; - const TaskId importTaskId = taskMgr->newTask([&](TaskProgress* progress) { - ErrorMessageCollect errorCollect; - okImport = app->ioSystem()->importInDocument() - .targetDocument(doc) - .withFilepaths(args.listFilepathToOpen) - .withParametersProvider(appModule) - .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { - appModule->computeBRepMesh(labelEntity, progress); - }) - .withEntityPostProcessRequiredIf([=](IO::Format){ return brepMeshRequired; }) - .withEntityPostProcessInfoProgress(20, Main::textIdTr("Mesh BRep shapes")) - .withMessenger(&errorCollect) - .withTaskProgress(progress) - .execute(); - taskMgr->setTitle(progress->taskId(), okImport ? Main::textIdTr("Imported") : errorCollect.message()); - helper->mapTaskStatus.at(progress->taskId())->success = okImport; - helper->mapTaskStatus.at(progress->taskId())->finished = true; - }); - helper->mapTaskStatus.insert({ importTaskId, std::make_unique() }); - taskMgr->setTitle(importTaskId, Main::textIdTr("Importing...")); - taskMgr->exec(importTaskId, TaskAutoDestroy::Off); - if (!okImport) - return fnExit(EXIT_FAILURE); // Error - - // Run export operations(asynchronous) - for (const FilePath& filepath : args.listFilepathToExport) { - const std::string strFilename = filepath.filename().u8string(); - const TaskId taskId = taskMgr->newTask([=](TaskProgress* progress) { - ErrorMessageCollect errorCollect; - const IO::Format format = app->ioSystem()->probeFormat(filepath); - const ApplicationItem appItems[] = { doc }; - const bool okExport = app->ioSystem()->exportApplicationItems() - .targetFile(filepath) - .targetFormat(format) - .withItems(appItems) - .withParameters(appModule->findWriterParameters(format)) - .withMessenger(&errorCollect) - .withTaskProgress(progress) - .execute(); - const std::string msg = - okExport ? - fmt::format(Main::textIdTr("Exported {}"), strFilename) : - errorCollect.message(); - taskMgr->setTitle(progress->taskId(), msg); - helper->mapTaskStatus.at(progress->taskId())->success = okExport; - helper->mapTaskStatus.at(progress->taskId())->finished = true; - --(helper->exportTaskCount); - }); - helper->mapTaskStatus.insert({ taskId, std::make_unique() }); - taskMgr->setTitle(taskId, fmt::format(Main::textIdTr("Exporting {}..."), strFilename)); - } - - taskMgr->foreachTask([=](TaskId taskId) { - if (taskId != importTaskId) - taskMgr->run(taskId, TaskAutoDestroy::Off); - }); -} // Initializes and runs Mayo application static int runApp(QCoreApplication* qtApp) @@ -627,18 +400,44 @@ static int runApp(QCoreApplication* qtApp) if (!args.filepathLog.empty()) LogMessageHandler::instance().setOutputFilePath(args.filepathLog); + // Initialize AppModule + auto appModule = AppModule::get(); + 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); + } + // Initialize Base application - initOpenCascadeEnvironment("opencascade.conf"); - initBase(qtApp); auto app = Application::instance().get(); + app->addTranslator(&qtTranslate); // Set Qt i18n backend + initOpenCascadeEnvironment("opencascade.conf"); // Initialize Gui application auto guiApp = new GuiApplication(app); initGui(guiApp); - // Register AppModule - auto appModule = new AppModule(app); - app->settings()->setPropertyValueConversion(*appModule); + // Register providers to query document tree node properties + appModule->addPropertiesProvider(std::make_unique()); + appModule->addPropertiesProvider(std::make_unique()); + + // Register I/O objects + IO::System* ioSystem = appModule->ioSystem(); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryReader(std::make_unique()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(IO::GmioFactoryWriter::create()); + ioSystem->addFactoryWriter(std::make_unique(guiApp)); + IO::addPredefinedFormatProbes(ioSystem); + appModule->properties()->IO_bindParameters(ioSystem); + appModule->properties()->retranslate(); // Process CLI if (!args.listFilepathToExport.empty()) { @@ -646,10 +445,14 @@ static int runApp(QCoreApplication* qtApp) fnCriticalExit(Main::tr("No input files -> nothing to export")); guiApp->setAutomaticDocumentMapping(false); // GuiDocument objects aren't needed - app->settings()->resetAll(); - fnLoadAppSettings(app->settings()); + appModule->settings()->resetAll(); + fnLoadAppSettings(appModule->settings()); QTimer::singleShot(0, qtApp, [=]{ - cli_asyncExportDocuments(app, args, [=](int retcode) { qtApp->exit(retcode); }); + CliExportArgs cliArgs; + cliArgs.progressReport = args.cliProgressReport; + cliArgs.filesToOpen = args.listFilepathToOpen; + cliArgs.filesToExport = args.listFilepathToExport; + cli_asyncExportDocuments(app, cliArgs, [=](int retcode) { qtApp->exit(retcode); }); }); return qtApp->exec(); } @@ -657,7 +460,7 @@ static int runApp(QCoreApplication* qtApp) // Record recent files when documents are closed QObject::connect( guiApp, &GuiApplication::guiDocumentErased, - appModule, &AppModule::recordRecentFileThumbnail); + AppModule::get(), &AppModule::recordRecentFileThumbnail); // Register WidgetModelTreeBuilter prototypes WidgetModelTree::addPrototypeBuilder(std::make_unique()); @@ -685,11 +488,11 @@ static int runApp(QCoreApplication* qtApp) QTimer::singleShot(0, [&]{ mainWindow.openDocumentsFromList(args.listFilepathToOpen); }); } - app->settings()->resetAll(); - fnLoadAppSettings(app->settings()); + appModule->settings()->resetAll(); + fnLoadAppSettings(appModule->settings()); const int code = qtApp->exec(); appModule->recordRecentFileThumbnails(guiApp); - app->settings()->save(); + appModule->settings()->save(); return code; } @@ -702,6 +505,11 @@ static void onQtAppExit() #endif } +#ifdef MAYO_WITH_TESTS +// Defined in tests/runtests.cpp +int runTests(int argc, char* argv[]); +#endif + } // namespace Mayo int main(int argc, char* argv[]) @@ -724,7 +532,8 @@ int main(int argc, char* argv[]) #endif std::unique_ptr ptrApp( - Mayo::isAppCliMode ? new QCoreApplication(argc, argv) : new QApplication(argc, argv)); + Mayo::isAppCliMode ? new QCoreApplication(argc, argv) : new QApplication(argc, argv) + ); #if defined(Q_OS_WIN) && defined(NDEBUG) if (Mayo::isAppCliMode) { @@ -733,7 +542,7 @@ int main(int argc, char* argv[]) if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) { auto fnRedirectToConsole = [](DWORD hnd, FILE* file, const char* strPath) { if (GetStdHandle(hnd) != INVALID_HANDLE_VALUE) { - freopen(strPath, "w", file); + file = freopen(strPath, "w", file); setvbuf(file, nullptr, _IONBF, 0); } }; @@ -748,5 +557,13 @@ int main(int argc, char* argv[]) QCoreApplication::setOrganizationDomain("www.fougue.pro"); QCoreApplication::setApplicationName("Mayo"); QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); + +#ifdef MAYO_WITH_TESTS + for (int i = 0; i < argc; ++i) { + if (std::strcmp(argv[i], "--runtests") == 0) + return Mayo::runTests(argc, argv); + } +#endif + return Mayo::runApp(ptrApp.get()); } diff --git a/src/app/mainwindow.cpp b/src/app/mainwindow.cpp index 0e21a69c..e649f0c2 100644 --- a/src/app/mainwindow.cpp +++ b/src/app/mainwindow.cpp @@ -22,12 +22,14 @@ #include "../gui/gui_application.h" #include "../gui/gui_document.h" #include "../gui/gui_document_list_model.h" +#include "../gui/qtgui_utils.h" #include "app_module.h" #include "dialog_about.h" #include "dialog_inspect_xde.h" #include "dialog_options.h" #include "dialog_save_image_view.h" #include "dialog_task_manager.h" +#include "document_property_group.h" #include "document_tree_node_properties_providers.h" #include "filepath_conv.h" #include "item_view_buttons.h" @@ -46,14 +48,14 @@ # include "windows/win_taskbar_global_progress.h" #endif -#include #include -#include +#include #include +#include #include #include #include -#include +#include // WARNING Qt5 / Qt6 #include #include #include @@ -90,12 +92,12 @@ static QString fileFilter(IO::Format format) static IO::Format formatFromFilter(const QString& filter) { - for (IO::Format format : Application::instance()->ioSystem()->readerFormats()) { + for (IO::Format format : AppModule::get()->ioSystem()->readerFormats()) { if (filter == fileFilter(format)) return format; } - for (IO::Format format : Application::instance()->ioSystem()->writerFormats()) { + for (IO::Format format : AppModule::get()->ioSystem()->writerFormats()) { if (filter == fileFilter(format)) return format; } @@ -111,15 +113,15 @@ struct ImportExportSettings { static ImportExportSettings load() { return { - AppModule::get(Application::instance())->lastOpenDir.value(), - to_QString(AppModule::get(Application::instance())->lastSelectedFormatFilter.value()) + AppModule::get()->properties()->lastOpenDir.value(), + to_QString(AppModule::get()->properties()->lastSelectedFormatFilter.value()) }; } static void save(const ImportExportSettings& sets) { - AppModule::get(Application::instance())->lastOpenDir.setValue(sets.openDir); - AppModule::get(Application::instance())->lastSelectedFormatFilter.setValue(to_stdString(sets.selectedFilter)); + AppModule::get()->properties()->lastOpenDir.setValue(sets.openDir); + AppModule::get()->properties()->lastSelectedFormatFilter.setValue(to_stdString(sets.selectedFilter)); } }; @@ -141,7 +143,7 @@ struct OpenFileNames { result.selectedFormat = IO::Format_Unknown; result.lastIoSettings = ImportExportSettings::load(); QStringList listFormatFilter; - for (IO::Format format : Application::instance()->ioSystem()->readerFormats()) + for (IO::Format format : AppModule::get()->ioSystem()->readerFormats()) listFormatFilter += fileFilter(format); const QString allFilesFilter = MainWindow::tr("All files(*.*)"); @@ -179,19 +181,13 @@ struct OpenFileNames { } }; -static void prependRecentFile(const FilePath& fp) -{ - auto appModule = AppModule::get(Application::instance()); - appModule->prependRecentFile(fp); -} - static void handleMessage(Messenger::MessageType msgType, const QString& text, QWidget* mainWnd) { switch (msgType) { case Messenger::MessageType::Trace: break; case Messenger::MessageType::Info: - WidgetMessageIndicator::showMessage(text, mainWnd); + WidgetMessageIndicator::showInfo(text, mainWnd); break; case Messenger::MessageType::Warning: WidgetsUtils::asyncMsgBoxWarning(mainWnd, MainWindow::tr("Warning"), text); @@ -207,7 +203,8 @@ static void handleMessage(Messenger::MessageType msgType, const QString& text, Q MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) : QMainWindow(parent), m_guiApp(guiApp), - m_ui(new Ui_MainWindow) + m_ui(new Ui_MainWindow), + m_taskMgr(new TaskManager(this)) { m_ui->setupUi(this); m_ui->widget_ModelTree->registerGuiApplication(guiApp); @@ -343,10 +340,10 @@ MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) QObject::connect( m_ui->actionToggleLeftSidebar, &QAction::toggled, this, &MainWindow::toggleLeftSidebar); - QObject::connect(m_ui->actionPreviousDoc, &QAction::triggered, [=]{ + QObject::connect(m_ui->actionPreviousDoc, &QAction::triggered, this, [=]{ this->setCurrentDocumentIndex(this->currentDocumentIndex() - 1); }); - QObject::connect(m_ui->actionNextDoc, &QAction::triggered, [=]{ + QObject::connect(m_ui->actionNextDoc, &QAction::triggered, this, [=]{ this->setCurrentDocumentIndex(this->currentDocumentIndex() + 1); }); QObject::connect( @@ -374,9 +371,9 @@ MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) this, &MainWindow::onApplicationItemSelectionChanged); QObject::connect( m_ui->listView_OpenedDocuments, &QListView::clicked, - [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); }); + this, [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); }); QObject::connect( - AppModule::get(guiApp->application()), &AppModule::message, + AppModule::get(), &AppModule::message, this, [=](Messenger::MessageType msgType, const QString& text) { Internal::handleMessage(msgType, text, this); }); @@ -393,15 +390,13 @@ MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) 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, - [=](int btnId, const QModelIndex& index) { + QObject::connect(listViewBtns, &ItemViewButtons::buttonClicked, this, [=](int btnId, QModelIndex index) { if (btnId == 1) this->closeDocument(index.row()); }); } - new DialogTaskManager(TaskManager::globalInstance(), this); + new DialogTaskManager(m_taskMgr, this); // BEWARE MainWindow::onGuiDocumentAdded() must be called before // MainWindow::onCurrentDocumentIndexChanged() @@ -477,11 +472,12 @@ void MainWindow::dropEvent(QDropEvent* event) void MainWindow::showEvent(QShowEvent* event) { QMainWindow::showEvent(event); -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) constexpr Qt::FindChildOption findMode = Qt::FindDirectChildrenOnly; auto winProgress = this->findChild(QString(), findMode); if (!winProgress) - winProgress = new WinTaskbarGlobalProgress(TaskManager::globalInstance(), this); + winProgress = new WinTaskbarGlobalProgress(m_taskMgr, this); + winProgress->setWindow(this->windowHandle()); #endif } @@ -510,19 +506,17 @@ void MainWindow::importInCurrentDoc() if (resFileNames.listFilepath.empty()) return; - auto app = m_guiApp->application(); - auto taskMgr = TaskManager::globalInstance(); - const TaskId taskId = taskMgr->newTask([=](TaskProgress* progress) { + auto appModule = AppModule::get(); + const TaskId taskId = m_taskMgr->newTask([=](TaskProgress* progress) { QElapsedTimer chrono; chrono.start(); - auto appModule = AppModule::get(app); - const bool okImport = app->ioSystem()->importInDocument() + const bool okImport = appModule->ioSystem()->importInDocument() .targetDocument(widgetGuiDoc->guiDocument()->document()) .withFilepaths(resFileNames.listFilepath) .withParametersProvider(appModule) .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { - AppModule::get(app)->computeBRepMesh(labelEntity, progress); + appModule->computeBRepMesh(labelEntity, progress); }) .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) .withEntityPostProcessInfoProgress(20, textIdTr("Mesh BRep shapes")) @@ -536,18 +530,17 @@ void MainWindow::importInCurrentDoc() resFileNames.listFilepath.size() > 1 ? tr("Import") : filepathTo(resFileNames.listFilepath.front().stem()); - taskMgr->setTitle(taskId, to_stdString(taskTitle)); - taskMgr->run(taskId); + m_taskMgr->setTitle(taskId, to_stdString(taskTitle)); + m_taskMgr->run(taskId); for (const FilePath& fp : resFileNames.listFilepath) - Internal::prependRecentFile(fp); + appModule->prependRecentFile(fp); } void MainWindow::exportSelectedItems() { - auto app = m_guiApp->application(); - + auto appModule = AppModule::get(); QStringList listWriterFileFilter; - for (IO::Format format : app->ioSystem()->writerFormats()) + for (IO::Format format : appModule->ioSystem()->writerFormats()) listWriterFileFilter.append(Internal::fileFilter(format)); auto lastSettings = Internal::ImportExportSettings::load(); @@ -562,14 +555,12 @@ void MainWindow::exportSelectedItems() return; lastSettings.openDir = filepathFrom(strFilepath); - auto taskMgr = TaskManager::globalInstance(); const IO::Format format = Internal::formatFromFilter(lastSettings.selectedFilter); - const TaskId taskId = taskMgr->newTask([=](TaskProgress* progress) { + const TaskId taskId = m_taskMgr->newTask([=](TaskProgress* progress) { QElapsedTimer chrono; chrono.start(); - auto appModule = AppModule::get(app); const bool okExport = - app->ioSystem()->exportApplicationItems() + appModule->ioSystem()->exportApplicationItems() .targetFile(filepathFrom(strFilepath)) .targetFormat(format) .withItems(m_guiApp->selectionModel()->selectedItems()) @@ -580,8 +571,8 @@ void MainWindow::exportSelectedItems() if (okExport) appModule->emitInfo(fmt::format(textIdTr("Export time: {}ms"), chrono.elapsed())); }); - taskMgr->setTitle(taskId, to_stdString(QFileInfo(strFilepath).fileName())); - taskMgr->run(taskId); + m_taskMgr->setTitle(taskId, to_stdString(QFileInfo(strFilepath).fileName())); + m_taskMgr->run(taskId); Internal::ImportExportSettings::save(lastSettings); } @@ -621,7 +612,7 @@ void MainWindow::zoomOutCurrentDoc() void MainWindow::editOptions() { - auto dlg = new DialogOptions(m_guiApp->application()->settings(), this); + auto dlg = new DialogOptions(AppModule::get()->settings(), this); WidgetsUtils::asyncDialogExec(dlg); } @@ -689,40 +680,44 @@ void MainWindow::onApplicationItemSelectionChanged() uiProps->clear(); Span spanAppItem = m_guiApp->selectionModel()->selectedItems(); if (spanAppItem.size() == 1) { - const ApplicationItem& item = spanAppItem.front(); - const DocumentTreeNode& docTreeNode = item.documentTreeNode(); - if (item.isDocumentTreeNode()) { - auto providerTable = m_guiApp->application()->documentTreeNodePropertiesProviderTable(); - m_ptrCurrentNodeDataProperties = providerTable->properties(docTreeNode); - PropertyGroupSignals* dataProps = m_ptrCurrentNodeDataProperties.get(); + 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, uiProps->addGroup(tr("Data"))); - QObject::connect(dataProps, &PropertyGroupSignals::propertyChanged, this, [=]{ - uiModelTree->refreshItemText(item); + uiProps->editProperties(dataProps.get(), uiProps->addGroup(tr("Data"))); + QObject::connect(dataProps.get(), &PropertyGroupSignals::propertyChanged, this, [=]{ + uiModelTree->refreshItemText(appItem); }); + m_ptrCurrentNodeDataProperties = std::move(dataProps); } - GuiDocument* guiDoc = m_guiApp->findGuiDocument(item.document()); + 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) { - m_ptrCurrentNodeGraphicsProperties = commonGfxDriver->properties(vecGfxObject); - GraphicsObjectBasePropertyGroup* gfxProps = m_ptrCurrentNodeGraphicsProperties.get(); + auto gfxProps = commonGfxDriver->properties(vecGfxObject); if (gfxProps) { - uiProps->editProperties(gfxProps, uiProps->addGroup(tr("Graphics"))); - QObject::connect(gfxProps, &PropertyGroupSignals::propertyChanged, this, [=]{ + uiProps->editProperties(gfxProps.get(), uiProps->addGroup(tr("Graphics"))); + QObject::connect(gfxProps.get(), &PropertyGroupSignals::propertyChanged, this, [=]{ guiDoc->graphicsScene()->redraw(); }); + m_ptrCurrentNodeGraphicsProperties = std::move(gfxProps); } } } auto app = m_guiApp->application(); - if (AppModule::get(app)->linkWithDocumentSelector.value()) { - const int index = app->findIndexOfDocument(item.document()); + if (AppModule::get()->properties()->linkWithDocumentSelector) { + const int index = app->findIndexOfDocument(appItem.document()); if (index != -1) this->setCurrentDocumentIndex(index); } @@ -738,32 +733,64 @@ void MainWindow::onApplicationItemSelectionChanged() void MainWindow::onOperationFinished(bool ok, const QString &msg) { if (ok) - WidgetMessageIndicator::showMessage(msg, this); + WidgetMessageIndicator::showInfo(msg, this); else WidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), msg); } void MainWindow::onGuiDocumentAdded(GuiDocument* guiDoc) { - auto app = m_guiApp->application(); - auto appModule = AppModule::get(app); + auto gfxScene = guiDoc->graphicsScene(); + // Configure highlighting aspect + auto fnConfigureHighlightStyle = [=](Prs3d_Drawer* drawer) { + const QColor fillAreaQColor = mayoTheme()->color(Theme::Color::Graphic3d_AspectFillArea); + if (!fillAreaQColor.isValid()) + return; + + auto fillArea = new Graphic3d_AspectFillArea3d; + auto defaultShadingAspect = gfxScene->drawerDefault()->ShadingAspect(); + if (defaultShadingAspect && defaultShadingAspect->Aspect()) + *fillArea = *defaultShadingAspect->Aspect(); + + const Quantity_Color fillAreaColor = QtGuiUtils::toPreferredColorSpace(fillAreaQColor); + fillArea->SetInteriorColor(fillAreaColor); + Graphic3d_MaterialAspect fillMaterial(Graphic3d_NOM_PLASTER); + fillMaterial.SetColor(fillAreaColor); + //fillMaterial.SetTransparency(0.1f); + fillArea->SetFrontMaterial(fillMaterial); + fillArea->SetBackMaterial(fillMaterial); + drawer->SetDisplayMode(AIS_Shaded); + drawer->SetBasicFillAreaAspect(fillArea); + }; + 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); - widget->controller()->setInstantZoomFactor(appModule->instantZoomFactor); - if (appModule->defaultShowOriginTrihedron.value()) { + auto widgetCtrl = widget->controller(); + widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); + widgetCtrl->setNavigationStyle(appProps->navigationStyle); + if (appProps->defaultShowOriginTrihedron) { guiDoc->toggleOriginTrihedronVisibility(); - guiDoc->graphicsScene()->redraw(); + gfxScene->redraw(); } - QObject::connect(app->settings(), &Settings::changed, this, [=](Property* setting) { - if (setting == &appModule->instantZoomFactor) - widget->controller()->setInstantZoomFactor(appModule->instantZoomFactor); + QObject::connect(appModule->settings(), &Settings::changed, this, [=](Property* setting) { + if (setting == &appProps->instantZoomFactor) + widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); + else if (setting == &appProps->navigationStyle) + widgetCtrl->setNavigationStyle(appProps->navigationStyle); }); - V3dViewController* ctrl = widget->controller(); - QObject::connect(ctrl, &V3dViewController::mouseMoved, [=](const QPoint& pos2d) { - guiDoc->graphicsScene()->highlightAt(pos2d, widget->guiDocument()->v3dView()); + // React to mouse move in 3D view: + // * update highlighting + // * compute and display 3D mouse coordinates(by silent picking) + QObject::connect(widgetCtrl, &V3dViewController::mouseMoved, this, [=](const QPoint& pos2d) { + gfxScene->highlightAt(pos2d, guiDoc->v3dView()); widget->view()->redraw(); - auto selector = guiDoc->graphicsScene()->mainSelector(); + auto selector = gfxScene->mainSelector(); selector->Pick(pos2d.x(), pos2d.y(), guiDoc->v3dView()); const gp_Pnt pos3d = selector->NbPicked() > 0 ? @@ -776,7 +803,7 @@ void MainWindow::onGuiDocumentAdded(GuiDocument* guiDoc) m_ui->stack_GuiDocuments->addWidget(widget); this->updateControlsActivation(); - const int newDocIndex = app->documentCount() - 1; + const int newDocIndex = m_guiApp->application()->documentCount() - 1; QTimer::singleShot(0, this, [=]{ this->setCurrentDocumentIndex(newDocIndex); }); } @@ -916,27 +943,20 @@ void MainWindow::openDocument(const FilePath& fp) void MainWindow::openDocumentsFromList(Span listFilePath) { auto app = m_guiApp->application(); - auto taskMgr = TaskManager::globalInstance(); - static std::mutex mutexApp; + auto appModule = AppModule::get(); for (const FilePath& fp : listFilePath) { - const DocumentPtr docPtr = app->findDocumentByLocation(fp); + DocumentPtr docPtr = app->findDocumentByLocation(fp); if (docPtr.IsNull()) { - const TaskId taskId = taskMgr->newTask([=](TaskProgress* progress) { + docPtr = app->newDocument(); + const TaskId taskId = m_taskMgr->newTask([=](TaskProgress* progress) { QElapsedTimer chrono; chrono.start(); - DocumentPtr doc; - { - std::lock_guard lock(mutexApp); MAYO_UNUSED(lock); - doc = app->newDocument(); - } - - doc->setName(fp.stem().u8string()); - doc->setFilePath(fp); + docPtr->setName(fp.stem().u8string()); + docPtr->setFilePath(fp); - auto appModule = AppModule::get(app); const bool okImport = - app->ioSystem()->importInDocument() - .targetDocument(doc) + appModule->ioSystem()->importInDocument() + .targetDocument(docPtr) .withFilepath(fp) .withParametersProvider(appModule) .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { @@ -950,9 +970,9 @@ void MainWindow::openDocumentsFromList(Span listFilePath) if (okImport) appModule->emitInfo(fmt::format(textIdTr("Import time: {}ms"), chrono.elapsed())); }); - taskMgr->setTitle(taskId, fp.stem().u8string()); - taskMgr->run(taskId); - Internal::prependRecentFile(fp); + m_taskMgr->setTitle(taskId, fp.stem().u8string()); + m_taskMgr->run(taskId); + AppModule::get()->prependRecentFile(fp); } else { if (listFilePath.size() == 1) @@ -1044,11 +1064,11 @@ QMenu* MainWindow::createMenuModelTreeSettings() menu->setToolTipsVisible(true); // Link with document selector - auto appModule = AppModule::get(m_guiApp->application()); - QAction* action = menu->addAction(to_QString(appModule->linkWithDocumentSelector.name().tr())); + auto appModule = AppModule::get(); + QAction* action = menu->addAction(to_QString(appModule->properties()->linkWithDocumentSelector.name().tr())); action->setCheckable(true); - QObject::connect(action, &QAction::triggered, [=](bool on) { - appModule->linkWithDocumentSelector.setValue(on); + QObject::connect(action, &QAction::triggered, this, [=](bool on) { + appModule->properties()->linkWithDocumentSelector.setValue(on); }); // Model tree user actions @@ -1059,7 +1079,7 @@ QMenu* MainWindow::createMenuModelTreeSettings() // Sync before menu show QObject::connect(menu, &QMenu::aboutToShow, this, [=]{ - action->setChecked(appModule->linkWithDocumentSelector.value()); + action->setChecked(appModule->properties()->linkWithDocumentSelector); if (userActions.fnSyncItems) userActions.fnSyncItems(); }); @@ -1075,8 +1095,8 @@ QMenu* MainWindow::createMenuRecentFiles() menu->clear(); int idFile = 0; - auto appModule = AppModule::get(m_guiApp->application()); - const RecentFiles& recentFiles = appModule->recentFiles.value(); + auto appModule = AppModule::get(); + const RecentFiles& recentFiles = appModule->properties()->recentFiles.value(); for (const RecentFile& recentFile : recentFiles) { const QString entryRecentFile = tr("%1 | %2").arg(++idFile).arg(filepathTo(recentFile.filepath)); menu->addAction(entryRecentFile, this, [=]{ this->openDocument(recentFile.filepath); }); @@ -1086,7 +1106,7 @@ QMenu* MainWindow::createMenuRecentFiles() menu->addSeparator(); menu->addAction(tr("Clear menu"), this, [=]{ menu->clear(); - appModule->recentFiles.setValue({}); + appModule->properties()->recentFiles.setValue({}); }); } @@ -1109,7 +1129,7 @@ QMenu* MainWindow::createMenuDisplayMode() if (!guiDoc) return menu; - const auto spanDrivers = m_guiApp->graphicsObjectDriverTable()->drivers(); + const auto spanDrivers = m_guiApp->graphicsObjectDrivers(); for (const GraphicsObjectDriverPtr& driver : spanDrivers) { if (driver->displayModes().empty()) continue; // Skip diff --git a/src/app/mainwindow.h b/src/app/mainwindow.h index 2eefe773..4c249162 100644 --- a/src/app/mainwindow.h +++ b/src/app/mainwindow.h @@ -9,16 +9,15 @@ #include "../base/filepath.h" #include "../base/property.h" #include "../base/text_id.h" -#include "../graphics/graphics_object_base_property_group.h" #include #include class QFileInfo; namespace Mayo { -class Document; class GuiApplication; class GuiDocument; +class TaskManager; class WidgetGuiDocument; class MainWindow : public QMainWindow { @@ -92,9 +91,10 @@ class MainWindow : public QMainWindow { GuiApplication* m_guiApp = nullptr; class Ui_MainWindow* m_ui = nullptr; + TaskManager* m_taskMgr = nullptr; Qt::WindowStates m_previousWindowState = Qt::WindowNoState; - std::unique_ptr m_ptrCurrentNodeDataProperties; - std::unique_ptr m_ptrCurrentNodeGraphicsProperties; + std::unique_ptr m_ptrCurrentNodeDataProperties; + std::unique_ptr m_ptrCurrentNodeGraphicsProperties; }; } // namespace Mayo diff --git a/src/app/measure_display.cpp b/src/app/measure_display.cpp new file mode 100644 index 00000000..3c69ec59 --- /dev/null +++ b/src/app/measure_display.cpp @@ -0,0 +1,376 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "measure_display.h" + +#include "app_module.h" +#include "measure_tool.h" +#include "qstring_conv.h" +#include "qstring_utils.h" + +#include +#include +#include + +#include + +namespace Mayo { + +namespace { +struct MeasureDisplayI18N { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::MeasureDisplayI18N) }; +} // namespace + +std::unique_ptr BaseMeasureDisplay::createFrom(MeasureType type, const MeasureValue& value) +{ + switch (type) { + case MeasureType::VertexPosition: + return std::make_unique(std::get(value)); + case MeasureType::CircleCenter: + return std::make_unique(std::get(value)); + case MeasureType::CircleDiameter: + return std::make_unique(std::get(value)); + case MeasureType::MinDistance: + return std::make_unique(std::get(value)); + case MeasureType::Length: + 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)); + default: + return {}; + } +} + +std::unique_ptr BaseMeasureDisplay::createEmptySumFrom(MeasureType type) +{ + switch (type) { + case MeasureType::Length: + return std::make_unique(Mayo::QuantityLength{0}); + case MeasureType::Area: + return std::make_unique(Mayo::QuantityArea{0}); + default: + return {}; + } +} + +void BaseMeasureDisplay::sumAdd(const IMeasureDisplay& /*other*/) +{ + ++m_sumCount; +} + +std::string_view BaseMeasureDisplay::sumTextOr(std::string_view singleItemText) const +{ + return m_sumCount > 1 ? MeasureDisplayI18N::textIdTr("Sum") : singleItemText; +} + +std::string BaseMeasureDisplay::text(const gp_Pnt& pnt, const MeasureConfig& config) +{ + auto trPntX = UnitSystem::translateLength(pnt.X() * Quantity_Millimeter, config.lengthUnit); + auto trPntY = UnitSystem::translateLength(pnt.Y() * Quantity_Millimeter, config.lengthUnit); + auto trPntZ = UnitSystem::translateLength(pnt.Z() * Quantity_Millimeter, config.lengthUnit); + const std::string str = fmt::format( + MeasureDisplayI18N::textIdTr("(X{0} " + "Y{1} " + "Z{2}){3}"), + text(trPntX), text(trPntY), text(trPntZ), + trPntX.strUnit + ); + return str; +} + +std::string BaseMeasureDisplay::text(double value) +{ + const QStringUtils::TextOptions textOpts = AppModule::get()->defaultTextOptions(); + return to_stdString(QStringUtils::text(value, textOpts)); +} + +std::string BaseMeasureDisplay::graphicsText(const gp_Pnt& pnt, const MeasureConfig& config) +{ + const std::string BaseMeasureDisplay = fmt::format( + MeasureDisplayI18N::textIdTr(" X{0} Y{1} Z{2}"), + text(UnitSystem::translateLength(pnt.X() * Quantity_Millimeter, config.lengthUnit)), + text(UnitSystem::translateLength(pnt.Y() * Quantity_Millimeter, config.lengthUnit)), + text(UnitSystem::translateLength(pnt.Z() * Quantity_Millimeter, config.lengthUnit)) + ); + return BaseMeasureDisplay; +} + +void BaseMeasureDisplay::applyGraphicsDefaults(IMeasureDisplay* measureDisplay) +{ + for (int i = 0; i < measureDisplay->graphicsObjectsCount(); ++i) { + auto gfxObject = measureDisplay->graphicsObjectAt(i); + auto gfxText = Handle_AIS_TextLabel::DownCast(gfxObject); + if (gfxText) { + gfxText->SetDisplayType(Aspect_TODT_SUBTITLE); + gfxText->SetColorSubTitle(Quantity_NOC_BLACK); + gfxText->SetColor(Quantity_NOC_WHITE); + gfxText->SetTransparency(0.2); + } + else { + gfxObject->SetColor(Quantity_NOC_BLACK); + } + } +} + +// -- +// -- Vertex +// -- + +MeasureDisplayVertex::MeasureDisplayVertex(const gp_Pnt& pnt) + : m_pnt(pnt), + m_gfxText(new AIS_TextLabel) +{ + m_gfxText->SetPosition(pnt); + BaseMeasureDisplay::applyGraphicsDefaults(this); +} + +void MeasureDisplayVertex::update(const MeasureConfig& config) +{ + this->setText(BaseMeasureDisplay::text(m_pnt, config)); + m_gfxText->SetText(to_OccExtString(BaseMeasureDisplay::graphicsText(m_pnt, config))); +} + +GraphicsObjectPtr MeasureDisplayVertex::graphicsObjectAt(int i) const +{ + return i == 0 ? m_gfxText : GraphicsObjectPtr{}; +} + +// -- +// -- CircleCenter +// -- + +MeasureDisplayCircleCenter::MeasureDisplayCircleCenter(const MeasureCircle& circle) + : m_circle(circle.value), + m_gfxPoint(new AIS_Point(new Geom_CartesianPoint(m_circle.Location()))), + m_gfxText(new AIS_TextLabel) +{ + m_gfxText->SetPosition(m_circle.Location()); + if (circle.isArc) { + m_gfxCircle = new AIS_Circle(new Geom_Circle(m_circle)); + m_gfxCircle->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); + } + + BaseMeasureDisplay::applyGraphicsDefaults(this); +} + +void MeasureDisplayCircleCenter::update(const MeasureConfig& config) +{ + this->setText(BaseMeasureDisplay::text(m_circle.Location(), config)); + m_gfxText->SetText(to_OccExtString(BaseMeasureDisplay::graphicsText(m_circle.Location(), config))); +} + +int MeasureDisplayCircleCenter::graphicsObjectsCount() const +{ + return m_gfxCircle ? 3 : 2; +} + +GraphicsObjectPtr MeasureDisplayCircleCenter::graphicsObjectAt(int i) const +{ + switch (i) { + case 0: return m_gfxPoint; + case 1: return m_gfxText; + case 2: return m_gfxCircle; + } + return GraphicsObjectPtr{}; +} + +// -- +// -- CircleDiameter +// -- + +MeasureDisplayCircleDiameter::MeasureDisplayCircleDiameter(const MeasureCircle& circle) + : m_circle(circle.value), + m_gfxCircle(new AIS_Circle(new Geom_Circle(m_circle))), + m_gfxDiameterText(new AIS_TextLabel) +{ + const gp_Pnt otherPntAnchor = diameterOpposedPnt(circle.pntAnchor, m_circle); + m_gfxDiameter = new AIS_Line(new Geom_CartesianPoint(circle.pntAnchor), new Geom_CartesianPoint(otherPntAnchor)); + m_gfxDiameter->SetWidth(2.5); + m_gfxDiameter->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); + + m_gfxDiameterText->SetPosition(m_circle.Location()); + + BaseMeasureDisplay::applyGraphicsDefaults(this); + m_gfxCircle->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); +} + +void MeasureDisplayCircleDiameter::update(const MeasureConfig& config) +{ + const QuantityLength diameter = 2 * m_circle.Radius() * Quantity_Millimeter; + const auto trDiameter = UnitSystem::translateLength(diameter, config.lengthUnit); + const auto strDiameter = BaseMeasureDisplay::text(trDiameter); + this->setText(fmt::format( + MeasureDisplayI18N::textIdTr("Diameter: {0}{1}"), strDiameter, trDiameter.strUnit + )); + + m_gfxDiameterText->SetText(to_OccExtString(fmt::format(MeasureDisplayI18N::textIdTr(" Ø{0}"), strDiameter))); + +} + +GraphicsObjectPtr MeasureDisplayCircleDiameter::graphicsObjectAt(int i) const +{ + switch (i) { + case 0: return m_gfxCircle; + case 1: return m_gfxDiameter; + case 2: return m_gfxDiameterText; + } + + return {}; +} + +gp_Pnt MeasureDisplayCircleDiameter::diameterOpposedPnt(const gp_Pnt& pntOnCircle, const gp_Circ& circ) +{ + return pntOnCircle.Translated(2 * gp_Vec{ pntOnCircle, circ.Location() }); +} + +// -- +// -- MinDistance +// -- + +MeasureDisplayMinDistance::MeasureDisplayMinDistance(const MeasureMinDistance& dist) + : m_dist(dist), + m_gfxLength(new AIS_Line(new Geom_CartesianPoint(dist.pnt1), new Geom_CartesianPoint(dist.pnt2))), + m_gfxDistText(new AIS_TextLabel), + m_gfxPnt1(new AIS_Point(new Geom_CartesianPoint(dist.pnt1))), + m_gfxPnt2(new AIS_Point(new Geom_CartesianPoint(dist.pnt2))) +{ + m_gfxLength->SetWidth(2.5); + m_gfxLength->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); + m_gfxDistText->SetPosition(dist.pnt1.Translated(gp_Vec(dist.pnt1, dist.pnt2) / 2.)); + BaseMeasureDisplay::applyGraphicsDefaults(this); +} + +void MeasureDisplayMinDistance::update(const MeasureConfig& config) +{ + const auto trLength = UnitSystem::translateLength(m_dist.value, config.lengthUnit); + const auto strLength = BaseMeasureDisplay::text(trLength); + this->setText(fmt::format( + MeasureDisplayI18N::textIdTr("Min Distance: {0}{1}
Point1: {2}
Point2: {3}"), + strLength, + trLength.strUnit, + BaseMeasureDisplay::text(m_dist.pnt1, config), + BaseMeasureDisplay::text(m_dist.pnt2, config) + )); + m_gfxDistText->SetText(to_OccExtString(" " + strLength)); +} + +GraphicsObjectPtr MeasureDisplayMinDistance::graphicsObjectAt(int i) const +{ + switch (i) { + case 0: return m_gfxLength; + case 1: return m_gfxDistText; + case 2: return m_gfxPnt1; + case 3: return m_gfxPnt2; + } + + return {}; +} + +// -- +// -- Angle +// -- + +MeasureDisplayAngle::MeasureDisplayAngle(MeasureAngle angle) + : m_angle(angle), + m_gfxEntity1(new AIS_Line(new Geom_CartesianPoint(angle.pntCenter), new Geom_CartesianPoint(angle.pnt1))), + m_gfxEntity2(new AIS_Line(new Geom_CartesianPoint(angle.pntCenter), new Geom_CartesianPoint(angle.pnt2))), + m_gfxAngleText(new AIS_TextLabel) +{ + const gp_Vec vec1(angle.pntCenter, angle.pnt1); + const gp_Vec vec2(angle.pntCenter, angle.pnt2); + const gp_Ax2 axCircle(angle.pntCenter, vec1.Crossed(vec2), vec1); + Handle_Geom_Circle geomCircle = new Geom_Circle(axCircle, 0.8 * vec1.Magnitude()); + const double param1 = ElCLib::Parameter(geomCircle->Circ(), angle.pnt1); + const double param2 = ElCLib::Parameter(geomCircle->Circ(), angle.pnt2); + m_gfxAngle = new AIS_Circle(geomCircle, param1, param2); + BaseMeasureDisplay::applyGraphicsDefaults(this); + m_gfxAngle->SetWidth(2); + m_gfxEntity1->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); + m_gfxEntity2->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); + m_gfxAngleText->SetPosition(ElCLib::Value(param1 + std::abs((param2 - param1) / 2.), geomCircle->Circ())); +} + +void MeasureDisplayAngle::update(const MeasureConfig& config) +{ + const auto trAngle = UnitSystem::translateAngle(m_angle.value, config.angleUnit); + const auto strAngle = BaseMeasureDisplay::text(trAngle); + this->setText(fmt::format(MeasureDisplayI18N::textIdTr("Angle: {0}{1}"), strAngle, trAngle.strUnit)); + m_gfxAngleText->SetText(to_OccExtString(" " + strAngle)); +} + +int MeasureDisplayAngle::graphicsObjectsCount() const +{ + return 4; +} + +GraphicsObjectPtr MeasureDisplayAngle::graphicsObjectAt(int i) const +{ + switch (i) { + case 0: return m_gfxAngle; + case 1: return m_gfxEntity1; + case 2: return m_gfxEntity2; + case 3: return m_gfxAngleText; + } + + return {}; +} + +// -- +// -- Length +// -- + +MeasureDisplayLength::MeasureDisplayLength(QuantityLength length) + : m_length(length) +{ +} + +void MeasureDisplayLength::update(const MeasureConfig& config) +{ + const auto trLength = UnitSystem::translateLength(m_length, config.lengthUnit); + this->setText(fmt::format( + MeasureDisplayI18N::textIdTr("{0}: {1}{2}"), + BaseMeasureDisplay::sumTextOr(MeasureDisplayI18N::textIdTr("Length")), + BaseMeasureDisplay::text(trLength), + trLength.strUnit + )); +} + +void MeasureDisplayLength::sumAdd(const IMeasureDisplay& other) +{ + const auto& otherLen = dynamic_cast(other); + m_length += otherLen.m_length; + BaseMeasureDisplay::sumAdd(other); +} + +// -- +// -- Area +// -- + +MeasureDisplayArea::MeasureDisplayArea(QuantityArea area) + : m_area(area) +{ +} + +void MeasureDisplayArea::update(const MeasureConfig& config) +{ + const auto trArea = UnitSystem::translateArea(m_area, config.areaUnit); + this->setText(fmt::format( + MeasureDisplayI18N::textIdTr("{0}: {1}{2}"), + BaseMeasureDisplay::sumTextOr(MeasureDisplayI18N::textIdTr("Area")), + BaseMeasureDisplay::text(trArea), + trArea.strUnit + )); +} + +void MeasureDisplayArea::sumAdd(const IMeasureDisplay& other) +{ + const auto& otherArea = dynamic_cast(other); + m_area += otherArea.m_area; + BaseMeasureDisplay::sumAdd(other); +} + +} // namespace Mayo diff --git a/src/app/measure_display.h b/src/app/measure_display.h new file mode 100644 index 00000000..addb37b1 --- /dev/null +++ b/src/app/measure_display.h @@ -0,0 +1,201 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "measure_tool.h" +#include "../graphics/graphics_object_ptr.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace Mayo { + +struct MeasureConfig { + LengthUnit lengthUnit = LengthUnit::Millimeter; + AngleUnit angleUnit = AngleUnit::Degree; + AreaUnit areaUnit = AreaUnit::SquareMillimeter; +}; + +// Provides an interface to textual/graphics representation of a measure +class IMeasureDisplay { +public: + virtual ~IMeasureDisplay() = default; + virtual void update(const MeasureConfig& config) = 0; + virtual std::string text() const = 0; + virtual int graphicsObjectsCount() const = 0; + virtual GraphicsObjectPtr graphicsObjectAt(int i) const = 0; + virtual bool isSumSupported() const = 0; + virtual void sumAdd(const IMeasureDisplay& other) = 0; +}; + +// Base class for IMeasureDisplay implementations +class BaseMeasureDisplay : public IMeasureDisplay { +public: + std::string text() const override { return m_text; } + + // Factory method to create an IMeasureDisplay object suited to input measure value + static std::unique_ptr createFrom(MeasureType type, const MeasureValue& value); + static std::unique_ptr createEmptySumFrom(MeasureType type); + + bool isSumSupported() const override { return false; } + void sumAdd(const IMeasureDisplay& other) override; + +protected: + void setText(std::string_view str) { m_text = str; } + + int sumCount() const { return m_sumCount; } + std::string_view sumTextOr(std::string_view singleItemText) const; + + static std::string text(const gp_Pnt& pnt, const MeasureConfig& config); + static std::string text(double value); + static std::string graphicsText(const gp_Pnt& pnt, const MeasureConfig& config); + + static void applyGraphicsDefaults(IMeasureDisplay* measureDisplay); + +private: + std::string m_text; + int m_sumCount = 0; +}; + +// -- +// -- Vertex +// -- + +class MeasureDisplayVertex : public BaseMeasureDisplay { +public: + MeasureDisplayVertex(const gp_Pnt& pnt); + void update(const MeasureConfig& config) override; + int graphicsObjectsCount() const override { return 1; } + GraphicsObjectPtr graphicsObjectAt(int i) const override; + +private: + gp_Pnt m_pnt; + Handle_AIS_TextLabel m_gfxText; +}; + +// -- +// -- CircleCenter +// -- + +class MeasureDisplayCircleCenter : public BaseMeasureDisplay { +public: + MeasureDisplayCircleCenter(const MeasureCircle& circle); + void update(const MeasureConfig& config) override; + int graphicsObjectsCount() const override; + GraphicsObjectPtr graphicsObjectAt(int i) const override; + +private: + gp_Circ m_circle; + Handle_AIS_Point m_gfxPoint; + Handle_AIS_TextLabel m_gfxText; + Handle_AIS_Circle m_gfxCircle; +}; + +// -- +// -- CircleDiameter +// -- + +class MeasureDisplayCircleDiameter : public BaseMeasureDisplay { +public: + MeasureDisplayCircleDiameter(const MeasureCircle& circle); + void update(const MeasureConfig& config) override; + int graphicsObjectsCount() const override { return 3; } + GraphicsObjectPtr graphicsObjectAt(int i) const override; + +private: + static gp_Pnt diameterOpposedPnt(const gp_Pnt& pntOnCircle, const gp_Circ& circ); + + gp_Circ m_circle; + Handle_AIS_Circle m_gfxCircle; + Handle_AIS_Line m_gfxDiameter; + Handle_AIS_TextLabel m_gfxDiameterText; +}; + +// -- +// -- MinDistance +// -- + +class MeasureDisplayMinDistance : public BaseMeasureDisplay { +public: + MeasureDisplayMinDistance(const MeasureMinDistance& dist); + void update(const MeasureConfig& config) override; + int graphicsObjectsCount() const override { return 4; } + GraphicsObjectPtr graphicsObjectAt(int i) const override; + +private: + MeasureMinDistance m_dist; + Handle_AIS_Line m_gfxLength; + Handle_AIS_TextLabel m_gfxDistText; + Handle_AIS_Point m_gfxPnt1; + Handle_AIS_Point m_gfxPnt2; +}; + +// -- +// -- Angle +// -- + +class MeasureDisplayAngle : public BaseMeasureDisplay { +public: + MeasureDisplayAngle(MeasureAngle angle); + void update(const MeasureConfig& config) override; + int graphicsObjectsCount() const override; + GraphicsObjectPtr graphicsObjectAt(int i) const override; + +private: + MeasureAngle m_angle; + Handle_AIS_Line m_gfxEntity1; + Handle_AIS_Line m_gfxEntity2; + Handle_AIS_Circle m_gfxAngle; + Handle_AIS_TextLabel m_gfxAngleText; +}; + +// -- +// -- Length +// -- + +class MeasureDisplayLength : public BaseMeasureDisplay { +public: + MeasureDisplayLength(QuantityLength length); + void update(const MeasureConfig& config) override; + int graphicsObjectsCount() const override { return 0; } + GraphicsObjectPtr graphicsObjectAt(int /*i*/) const override { return {}; } + + bool isSumSupported() const override { return true; } + void sumAdd(const IMeasureDisplay& other) override; + +private: + QuantityLength m_length; +}; + +// -- +// -- Area +// -- + +class MeasureDisplayArea : public BaseMeasureDisplay { +public: + MeasureDisplayArea(QuantityArea area); + void update(const MeasureConfig& config) override; + int graphicsObjectsCount() const override { return 0; } + GraphicsObjectPtr graphicsObjectAt(int /*i*/) const override { return {}; } + + bool isSumSupported() const override { return true; } + void sumAdd(const IMeasureDisplay& other) override; + +private: + QuantityArea m_area; + int m_sumCount = 0; +}; + +} // namespace Mayo diff --git a/src/app/measure_tool.cpp b/src/app/measure_tool.cpp new file mode 100644 index 00000000..8f9c007c --- /dev/null +++ b/src/app/measure_tool.cpp @@ -0,0 +1,54 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "measure_tool.h" + +namespace Mayo { + +MeasureValue IMeasureTool_computeValue( + const IMeasureTool& tool, MeasureType type, const GraphicsOwnerPtr& owner) +{ + MeasureValue value; + + switch (type) { + case MeasureType::VertexPosition: + return tool.vertexPosition(owner); + case MeasureType::CircleCenter: + case MeasureType::CircleDiameter: + return tool.circle(owner); + case MeasureType::Length: + return tool.length(owner); + case MeasureType::Area: + return tool.area(owner); + } // endswitch + + return value; +} + +MeasureValue IMeasureTool_computeValue( + const IMeasureTool& tool, + MeasureType type, + const GraphicsOwnerPtr& owner1, + const GraphicsOwnerPtr& owner2) +{ + MeasureValue value; + + switch (type) { + case MeasureType::MinDistance: + return tool.minDistance(owner1, owner2); + case MeasureType::Angle: + return tool.angle(owner1, owner2); + } // endswitch + + return value; +} + +bool MeasureValue_isValid(const MeasureValue& value) +{ + return !std::holds_alternative(value); +} + +} // namespace Mayo diff --git a/src/app/measure_tool.h b/src/app/measure_tool.h new file mode 100644 index 00000000..06197edc --- /dev/null +++ b/src/app/measure_tool.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "measure_type.h" +#include "../base/quantity.h" +#include "../base/span.h" +#include "../graphics/graphics_object_ptr.h" +#include "../graphics/graphics_owner_ptr.h" + +#include +#include +#include + +#include +#include + +namespace Mayo { + +// Void measure value +struct MeasureNone {}; + +// Measure of minimum distance between two entities +struct MeasureMinDistance { + // Point on 1st entity where minimum distance is located + gp_Pnt pnt1; + // Point on 2nd entity where minimum distance is located + gp_Pnt pnt2; + // Length of the minimum distance + QuantityLength value; +}; + +// Measure of a circle entity +struct MeasureCircle { + // "Start" point of the circle + gp_Pnt pntAnchor; + // Whether entity is a portion of circle(arc) + bool isArc = false; + // Circle definition + gp_Circ value; +}; + +// Measure of the angle between two linear entities +struct MeasureAngle { + // Point on the 1st entity forming the angle + gp_Pnt pnt1; + // Point on the 2nd entity forming the angle + gp_Pnt pnt2; + // Origin of the angle + gp_Pnt pntCenter; + // Value of the angle + QuantityAngle 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 { +public: + virtual ~IMeasureTool() = default; + + virtual Span selectionModes(MeasureType type) const = 0; + virtual bool supports(const GraphicsObjectPtr& object) const = 0; + virtual bool supports(MeasureType type) const = 0; + + 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 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; +}; + +// Base interface for errors reported by measurement services of IMeasureTool +class IMeasureError { +public: + virtual std::string_view message() const = 0; +}; + +// Union type for all the various measure values returned by IMeasureTool services +using MeasureValue = std::variant< + MeasureNone, // Warning: ensure this is the first value type in the variant + gp_Pnt, + MeasureCircle, + MeasureMinDistance, + MeasureAngle, + QuantityLength, + QuantityArea + >; + +bool MeasureValue_isValid(const MeasureValue& res); + +MeasureValue IMeasureTool_computeValue( + const IMeasureTool& tool, + MeasureType type, + const GraphicsOwnerPtr& owner +); + +MeasureValue IMeasureTool_computeValue( + const IMeasureTool& tool, + MeasureType type, + const GraphicsOwnerPtr& owner1, + const GraphicsOwnerPtr& owner2 +); + +} // namespace Mayo diff --git a/src/app/measure_tool_brep.cpp b/src/app/measure_tool_brep.cpp new file mode 100644 index 00000000..b34eef5c --- /dev/null +++ b/src/app/measure_tool_brep.cpp @@ -0,0 +1,307 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "measure_tool_brep.h" + +#include "../base/geom_utils.h" +#include "../base/text_id.h" +#include "../graphics/graphics_shape_object_driver.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#if OCC_VERSION_HEX >= 0x070500 +# include +#else +# include +using PrsDim_AngleDimension = AIS_AngleDimension; +#endif + +#include +#include + +namespace Mayo { + +namespace { + +enum class ErrorCode { + Unknown, + NotVertex, + NotCircularEdge, + NotBRepShape, + MinDistanceFailure, + NotAllEdges, + NotLinearEdge, + NotAllFaces, + ParallelEdges +}; + +template +class BRepMeasureError : public IMeasureError { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::BRepMeasureError) +public: + std::string_view message() const override + { + switch (ERRCODE) { + case ErrorCode::NotVertex: + return textIdTr("Entity must be a vertex"); + case ErrorCode::NotCircularEdge: + return textIdTr("Entity must be a circular edge"); + case ErrorCode::NotBRepShape: + return textIdTr("Entity must be a shape(BREP)"); + case ErrorCode::MinDistanceFailure: + return textIdTr("Computation of minimum distance failed"); + case ErrorCode::NotAllEdges: + return textIdTr("All entities must be edges"); + case ErrorCode::NotLinearEdge: + return textIdTr("Entity must be a linear edge"); + case ErrorCode::NotAllFaces: + return textIdTr("All entities must be faces"); + case ErrorCode::ParallelEdges: + return textIdTr("Entities must not be parallel"); + default: + return textIdTr("Unknown error"); + } + } +}; + +template void throwErrorIf(bool cond) +{ + if (cond) + throw BRepMeasureError(); +} + +const TopoDS_Shape& getShape(const GraphicsOwnerPtr& owner) +{ + static const TopoDS_Shape nullShape; + auto brepOwner = Handle_StdSelect_BRepOwner::DownCast(owner); + return brepOwner ? brepOwner->Shape() : nullShape; +} + +} // namespace + +Span MeasureToolBRep::selectionModes(MeasureType type) const +{ + switch (type) { + case MeasureType::VertexPosition: { + static const GraphicsObjectSelectionMode modes[] = { AIS_Shape::SelectionMode(TopAbs_VERTEX) }; + return modes; + } + case MeasureType::CircleCenter: + case MeasureType::CircleDiameter: + case MeasureType::Length: + case MeasureType::Angle: { + static const GraphicsObjectSelectionMode modes[] = { AIS_Shape::SelectionMode(TopAbs_EDGE) }; + return modes; + } + case MeasureType::MinDistance: { + static const GraphicsObjectSelectionMode modes[] = { + AIS_Shape::SelectionMode(TopAbs_VERTEX), + AIS_Shape::SelectionMode(TopAbs_EDGE), + AIS_Shape::SelectionMode(TopAbs_FACE) + }; + return modes; + } + case MeasureType::Area: { + static const GraphicsObjectSelectionMode modes[] = { AIS_Shape::SelectionMode(TopAbs_FACE) }; + return modes; + } + } // endswitch + + return {}; +} + +bool MeasureToolBRep::supports(const GraphicsObjectPtr& object) const +{ + auto gfxDriver = GraphicsObjectDriver::get(object); + return gfxDriver ? !GraphicsShapeObjectDriverPtr::DownCast(gfxDriver).IsNull() : false; +} + +bool MeasureToolBRep::supports(MeasureType type) const +{ + return type != MeasureType::None; +} + +gp_Pnt MeasureToolBRep::vertexPosition(const GraphicsOwnerPtr& owner) const +{ + const TopoDS_Shape& shape = getShape(owner); + if (shape.IsNull() || shape.ShapeType() != TopAbs_VERTEX) + throw BRepMeasureError(); + + return BRep_Tool::Pnt(TopoDS::Vertex(shape)).Transformed(owner->Location()); +} + +MeasureCircle MeasureToolBRep::circle(const GraphicsOwnerPtr& owner) const +{ + auto fnThrowErrorIf = [](bool cond) { + throwErrorIf(cond); + }; + + const TopoDS_Shape& shape = getShape(owner); + fnThrowErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_EDGE); + + const BRepAdaptor_Curve curve(TopoDS::Edge(shape)); + std::optional circle; + if (curve.GetType() == GeomAbs_Circle) { + circle = curve.Circle(); + } + else if (curve.GetType() == GeomAbs_Ellipse) { + const gp_Elips ellipse = curve.Ellipse(); + if (std::abs(ellipse.MinorRadius() - ellipse.MajorRadius()) < Precision::Confusion()) + circle = gp_Circ{ ellipse.Position(), ellipse.MinorRadius() }; + } + else { + // Try to create a circle from 3 sample points on the curve + { + const GCPnts_QuasiUniformAbscissa pnts(curve, 4); // More points to avoid confusion + fnThrowErrorIf(!pnts.IsDone() || pnts.NbPoints() < 3); + const GC_MakeCircle makeCirc( + GeomUtils::d0(curve, pnts.Parameter(1)), + GeomUtils::d0(curve, pnts.Parameter(2)), + GeomUtils::d0(curve, pnts.Parameter(3)) + ); + fnThrowErrorIf(!makeCirc.IsDone()); + circle = makeCirc.Value()->Circ(); + } + + // Take more sample points on the curve and check for each that: + // dist(pntSample, pntCircleCenter) - circleRadius < tolerance + { + const GCPnts_QuasiUniformAbscissa pnts(curve, 64); + fnThrowErrorIf(!pnts.IsDone()); + for (int i = 1; i <= pnts.NbPoints(); ++i) { + const gp_Pnt pntSample = GeomUtils::d0(curve, pnts.Parameter(i)); + const double dist = pntSample.Distance(circle->Location()); + fnThrowErrorIf(std::abs(dist - circle->Radius()) > 1e-4); + } + } + } + + fnThrowErrorIf(!circle); + MeasureCircle result; + result.pntAnchor = GeomUtils::d0(curve, curve.FirstParameter()).Transformed(owner->Location()); + result.isArc = !curve.IsClosed(); + result.value = circle->Transformed(owner->Location()); + return result; +} + +MeasureMinDistance MeasureToolBRep::minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +{ + const TopoDS_Shape shape1 = getShape(owner1).Moved(owner1->Location()); + const TopoDS_Shape shape2 = getShape(owner2).Moved(owner2->Location()); + throwErrorIf(shape1.IsNull()); + throwErrorIf(shape2.IsNull()); + + const BRepExtrema_DistShapeShape dist(shape1, shape2); + throwErrorIf(!dist.IsDone()); + + MeasureMinDistance distResult; + distResult.pnt1 = dist.PointOnShape1(1); + distResult.pnt2 = dist.PointOnShape2(1); + distResult.value = dist.Value() * Quantity_Millimeter; + return distResult; +} + +MeasureAngle MeasureToolBRep::angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +{ + MeasureAngle angleResult; + + const TopoDS_Shape& shape1 = getShape(owner1); + const TopoDS_Shape& shape2 = getShape(owner2); + throwErrorIf(shape1.IsNull()); + throwErrorIf(shape2.IsNull()); + + TopoDS_Edge edge1 = TopoDS::Edge(shape1); + TopoDS_Edge edge2 = TopoDS::Edge(shape2); + const BRepAdaptor_Curve curve1(edge1); + const BRepAdaptor_Curve curve2(edge2); + + auto fnGetLineDirection = [](const Adaptor3d_Curve& curve) -> gp_Dir { + throwErrorIf(curve.GetType() != GeomAbs_Line); + return curve.Line().Direction(); +#if 0 + // Find the direction in case the curve is a pseudo line + const GCPnts_QuasiUniformAbscissa pnts(curve, 64); + throwErrorIf(!pnts.IsDone()); + const gp_Pnt pntFirst = GeomUtils::d0(curve, pnts.Parameter(1)); + const gp_Pnt pntLast = GeomUtils::d0(curve, pnts.Parameter(pnts.NbPoints())); + throwErrorIf(pntFirst.Distance(pntLast) < Precision::Confusion()); + const gp_Dir dirLine(gp_Vec(pntFirst, pntLast)); + for (int i = 2; i <= pnts.NbPoints() - 1; ++i) { + const gp_Pnt pntSample = GeomUtils::d0(curve, pnts.Parameter(i)); + throwErrorIf(pntFirst.Distance(pntSample) < Precision::Confusion()); + const gp_Dir dirSample(gp_Vec(pntFirst, pntSample)); + throwErrorIf(!dirLine.IsEqual(dirSample, Precision::Angular())); + } + return dirLine; +#endif + }; + + // Check edges are not parallel + const gp_Dir dirLine1 = fnGetLineDirection(curve1); + const gp_Dir dirLine2 = fnGetLineDirection(curve2); + throwErrorIf(dirLine1.IsParallel(dirLine2, Precision::Angular())); + + // Let 'edge1' be the smallest entity regarding length + if (GCPnts_AbscissaPoint::Length(curve1, 1e-6) > GCPnts_AbscissaPoint::Length(curve2, 1e-6)) + std::swap(edge1, edge2); + + // Move 'edge2' close to 'edge1' if needed + BRepExtrema_DistShapeShape distEdges(edge1, edge2); + throwErrorIf(!distEdges.IsDone()); + const double minDist = distEdges.Value(); + if (minDist > Precision::Confusion()) { + gp_Trsf trsf; + trsf.SetTranslation(distEdges.PointOnShape2(1), distEdges.PointOnShape1(1)); + edge2 = TopoDS::Edge(BRepBuilderAPI_Transform(edge2, trsf, true/*copy*/)); + } + + // Compute angle by delegating to PrsDim_AngleDimension + PrsDim_AngleDimension dimAngle(edge1, edge2); + angleResult.pnt1 = dimAngle.FirstPoint().Transformed(owner1->Location()); + angleResult.pnt2 = dimAngle.SecondPoint().Transformed(owner2->Location()); + angleResult.pntCenter = dimAngle.CenterPoint().Transformed(owner1->Location()); + const gp_Vec vec1(angleResult.pntCenter, angleResult.pnt1); + const gp_Vec vec2(angleResult.pntCenter, angleResult.pnt2); + angleResult.value = vec1.Angle(vec2) * Quantity_Radian; + + return angleResult; +} + +QuantityLength MeasureToolBRep::length(const GraphicsOwnerPtr& owner) const +{ + const TopoDS_Shape& shape = getShape(owner); + throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_EDGE); + const BRepAdaptor_Curve curve(TopoDS::Edge(shape)); + const double len = GCPnts_AbscissaPoint::Length(curve, 1e-6); + return len * Quantity_Millimeter; +} + +QuantityArea 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; +} + +} // namespace Mayo diff --git a/src/app/measure_tool_brep.h b/src/app/measure_tool_brep.h new file mode 100644 index 00000000..33dce1a1 --- /dev/null +++ b/src/app/measure_tool_brep.h @@ -0,0 +1,28 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "measure_tool.h" + +namespace Mayo { + +// Provides measurement services for BRep shapes +class MeasureToolBRep : public IMeasureTool { +public: + Span selectionModes(MeasureType type) const override; + bool supports(const GraphicsObjectPtr& object) const override; + bool supports(MeasureType type) const override; + + 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; + MeasureAngle angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const override; + QuantityLength length(const GraphicsOwnerPtr& owner) const override; + QuantityArea area(const GraphicsOwnerPtr& owner) const override; +}; + +} // namespace Mayo diff --git a/src/app/measure_type.h b/src/app/measure_type.h new file mode 100644 index 00000000..0684cfc9 --- /dev/null +++ b/src/app/measure_type.h @@ -0,0 +1,15 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +namespace Mayo { + +enum class MeasureType { + None, VertexPosition, CircleCenter, CircleDiameter, MinDistance, Angle, Length, Area +}; + +} // namespace Mayo diff --git a/src/app/property_editor_factory.cpp b/src/app/property_editor_factory.cpp index 220d92d9..c0ffdc85 100644 --- a/src/app/property_editor_factory.cpp +++ b/src/app/property_editor_factory.cpp @@ -105,7 +105,7 @@ struct PropertyDoubleEditor : public InterfacePropertyEditor, public QDoubleSpin this->setButtonSymbols(QAbstractSpinBox::NoButtons); } - this->setDecimals(AppModule::get(Application::instance())->unitSystemDecimals.value()); + this->setDecimals(AppModule::get()->properties()->unitSystemDecimals.value()); QObject::connect(this, qOverload(&QDoubleSpinBox::valueChanged), [=](double val) { property->setValue(val); }); @@ -267,8 +267,8 @@ struct Property3dCoordsEditor : public InterfacePropertyEditor, public QWidget { void syncWithProperty() override { auto fnSetEditorCoord = [](QDoubleSpinBox* editor, double coord){ - auto appModule = AppModule::get(Application::instance()); - auto trRes = UnitSystem::translate(appModule->unitSystemSchema, coord * Quantity_Millimeter); + auto appModule = AppModule::get(); + auto trRes = UnitSystem::translate(appModule->properties()->unitSystemSchema, coord * Quantity_Millimeter); QSignalBlocker _(editor); editor->setValue(trRes.value); }; @@ -281,10 +281,10 @@ struct Property3dCoordsEditor : public InterfacePropertyEditor, public QWidget { static QDoubleSpinBox* createCoordEditor(QWidget* parentWidget, PropertyCoordsType* property, Coord specCoord) { auto editor = new QDoubleSpinBox(parentWidget); - auto appModule = AppModule::get(Application::instance()); - auto trRes = UnitSystem::translate(appModule->unitSystemSchema, 1., Unit::Length); + auto appModule = AppModule::get(); + auto trRes = UnitSystem::translate(appModule->properties()->unitSystemSchema, 1., Unit::Length); //editor->setSuffix(QString::fromUtf8(trRes.strUnit)); - editor->setDecimals(appModule->unitSystemDecimals.value()); + editor->setDecimals(appModule->properties()->unitSystemDecimals); editor->setButtonSymbols(QDoubleSpinBox::NoButtons); editor->setRange(-std::numeric_limits::max(), std::numeric_limits::max()); QSizePolicy sp = editor->sizePolicy(); @@ -318,7 +318,7 @@ struct PropertyQuantityEditor : public InterfacePropertyEditor, public QDoubleSp { const UnitSystem::TranslateResult trRes = PropertyEditorFactory::unitTranslate(property); this->setSuffix(QString::fromUtf8(trRes.strUnit)); - this->setDecimals(AppModule::get(Application::instance())->unitSystemDecimals.value()); + this->setDecimals(AppModule::get()->properties()->unitSystemDecimals.value()); const double rangeMin = property->constraintsEnabled() ? property->minimum() : std::numeric_limits::min(); @@ -403,7 +403,7 @@ UnitSystem::TranslateResult PropertyEditorFactory::unitTranslate(const BasePrope } return UnitSystem::translate( - AppModule::get(Application::instance())->unitSystemSchema, + AppModule::get()->properties()->unitSystemSchema, property->quantityValue(), property->quantityUnit()); } diff --git a/src/app/property_item_delegate.cpp b/src/app/property_item_delegate.cpp index aa8af297..6cb63091 100644 --- a/src/app/property_item_delegate.cpp +++ b/src/app/property_item_delegate.cpp @@ -11,10 +11,12 @@ #include "../base/unit_system.h" #include "../gui/qtgui_utils.h" #include "app_module.h" +#include "filepath_conv.h" #include "qstring_conv.h" #include "qstring_utils.h" #include "theme.h" +#include #include #include #include @@ -57,7 +59,7 @@ class PanelEditor : public QWidget { static QStringUtils::TextOptions appDefaultTextOptions() { - return AppModule::get(Application::instance())->defaultTextOptions(); + return AppModule::get()->defaultTextOptions(); } static QString toStringDHMS(QuantityTime time) @@ -92,7 +94,7 @@ static QString propertyValueText(const PropertyBool* prop) { } static QString propertyValueText(const PropertyInt* prop) { - return AppModule::get(Application::instance())->locale().toString(prop->value()); + return AppModule::get()->locale().toString(prop->value()); } static QString propertyValueText(const PropertyDouble* prop) { @@ -107,6 +109,11 @@ static QString propertyValueText(const PropertyString* prop) { return to_QString(prop->value()); } +static QString propertyValueText(const PropertyFilePath* prop) { + const FilePath filepath = filepathCanonical(prop->value()); + return QDir::toNativeSeparators(filepathTo(filepath)); +} + static QString propertyValueText(const PropertyOccColor* prop) { return QStringUtils::text(prop->value()); } @@ -254,6 +261,9 @@ QString PropertyItemDelegate::displayText(const QVariant& value, const QLocale&) if (propTypeName == PropertyString::TypeName) return propertyValueText(static_cast(prop)); + if (propTypeName == PropertyFilePath::TypeName) + return propertyValueText(static_cast(prop)); + if (propTypeName == PropertyOccColor::TypeName) return propertyValueText(static_cast(prop)); diff --git a/src/app/qstring_utils.cpp b/src/app/qstring_utils.cpp index a00fcef4..10223e2e 100644 --- a/src/app/qstring_utils.cpp +++ b/src/app/qstring_utils.cpp @@ -21,7 +21,12 @@ static QString valueText(double value, const QStringUtils::TextOptions& opt) const double c = std::abs(value) < Precision::Confusion() ? 0. : value; QString str = opt.locale.toString(c, 'f', opt.unitDecimals); +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) + // TODO Consider QString decimal point + const QChar chDecPnt = opt.locale.decimalPoint().front(); +#else const QChar chDecPnt = opt.locale.decimalPoint(); +#endif const int posPnt = str.indexOf(chDecPnt); if (posPnt != -1) { // Remove useless trailing zeroes while (fnLastChar(str) == opt.locale.zeroDigit()) @@ -44,8 +49,7 @@ static QString coordsText(const gp_XYZ& coords, const QStringUtils::TextOptions& static QString pntCoordText(double coord, const QStringUtils::TextOptions& opt) { - const UnitSystem::TranslateResult trCoord = - UnitSystem::translate(opt.unitSchema, coord * Quantity_Millimeter); + const auto trCoord = UnitSystem::translate(opt.unitSchema, coord * Quantity_Millimeter); const QString strValue = valueText(trCoord.value, opt); return strValue + trCoord.strUnit; } @@ -118,6 +122,7 @@ QString QStringUtils::yesNoText(CheckState state) case CheckState::Partially: return tr("Partially"); case CheckState::On: return tr("Yes"); } + return {}; } void QStringUtils::append(QString* dst, const QString& str, const QLocale& locale) diff --git a/src/app/recent_files.cpp b/src/app/recent_files.cpp index ad2e4359..e9988fd8 100644 --- a/src/app/recent_files.cpp +++ b/src/app/recent_files.cpp @@ -93,7 +93,11 @@ QDataStream& operator>>(QDataStream& stream, RecentFile& recentFile) stream >> strFilepath; recentFile.filepath = filepathFrom(strFilepath); stream >> recentFile.thumbnail; - stream >> reinterpret_cast(recentFile.thumbnailTimestamp); + // Read thumbnail timestamp + // Warning: qint64 and int64_t may not be the exact same type(eg __int64 and longlong with Windows/MSVC) + qint64 timestamp; + stream >> timestamp; + recentFile.thumbnailTimestamp = timestamp; return stream; } diff --git a/src/app/theme.cpp b/src/app/theme.cpp index 339dd40e..f554716c 100644 --- a/src/app/theme.cpp +++ b/src/app/theme.cpp @@ -19,6 +19,12 @@ namespace Mayo { namespace Internal { +static const QIcon& nullQIcon() +{ + static const QIcon null; + return null; +} + static QString cssFlatComboBox( const QString& urlPixDownArrow, const QString& urlPixDownArrowDisabled) @@ -86,6 +92,7 @@ static QString iconFileName(Theme::Icon icn) case Theme::Icon::ZoomIn: return "zoom-in.svg"; case Theme::Icon::ZoomOut: return "zoom-out.svg"; case Theme::Icon::ClipPlane: return "clip-plane.svg"; + case Theme::Icon::Measure: return "measure.svg"; case Theme::Icon::View3dIso: return "view-iso.svg"; case Theme::Icon::View3dLeft: return "view-left.svg"; case Theme::Icon::View3dRight: return "view-right.svg"; @@ -101,8 +108,6 @@ static QString iconFileName(Theme::Icon icn) return QString(); } -static const QIcon nullQIcon = {}; - class ThemeClassic : public Theme { public: QColor color(Color role) const override @@ -126,6 +131,8 @@ class ThemeClassic : public Theme { case Theme::Color::ButtonView3d_Hover: case Theme::Color::ButtonView3d_Checked: return QColor(65, 200, 250); + case Theme::Color::Graphic3d_AspectFillArea: + return QColor(128, 200, 255); case Theme::Color::View3d_BackgroundGradientStart: return QColor(128, 148, 255); case Theme::Color::View3d_BackgroundGradientEnd: @@ -134,10 +141,13 @@ class ThemeClassic : public Theme { return QColor(65, 200, 250); case Theme::Color::RubberBandView3d_Fill: return QColor(65, 200, 250).lighter(); - case Theme::Color::MessageIndicator_Background: + case Theme::Color::MessageIndicator_InfoBackground: return QColor(128, 200, 255); - case Theme::Color::MessageIndicator_Text: + case Theme::Color::MessageIndicator_InfoText: + case Theme::Color::MessageIndicator_ErrorText: return appPalette.color(QPalette::WindowText); + case Theme::Color::MessageIndicator_ErrorBackground: + return QColor(225, 127, 127, 140); } return QColor(); } @@ -145,7 +155,7 @@ class ThemeClassic : public Theme { const QIcon& icon(Icon icn) const override { auto it = m_mapIcon.find(icn); - return it != m_mapIcon.cend() ? it->second : nullQIcon; + return it != m_mapIcon.cend() ? it->second : nullQIcon(); } void setup() override @@ -191,6 +201,8 @@ class ThemeDark : public Theme { return appPalette.color(QPalette::Button).lighter(160); case Theme::Color::ButtonView3d_Checked: return appPalette.color(QPalette::Highlight); + case Theme::Color::Graphic3d_AspectFillArea: + return appPalette.color(QPalette::Highlight); case Theme::Color::View3d_BackgroundGradientStart: return QColor(100, 100, 100); case Theme::Color::View3d_BackgroundGradientEnd: @@ -199,10 +211,13 @@ class ThemeDark : public Theme { return appPalette.color(QPalette::Highlight); case Theme::Color::RubberBandView3d_Fill: return appPalette.color(QPalette::Highlight).lighter(); - case Theme::Color::MessageIndicator_Background: + case Theme::Color::MessageIndicator_InfoBackground: return appPalette.color(QPalette::Highlight); - case Theme::Color::MessageIndicator_Text: + case Theme::Color::MessageIndicator_InfoText: + case Theme::Color::MessageIndicator_ErrorText: return appPalette.color(QPalette::WindowText); + case Theme::Color::MessageIndicator_ErrorBackground: + return QColor(225, 127, 127, 140); } return QColor(); } @@ -210,7 +225,7 @@ class ThemeDark : public Theme { const QIcon& icon(Icon icn) const override { auto it = m_mapIcon.find(icn); - return it != m_mapIcon.cend() ? it->second : nullQIcon; + return it != m_mapIcon.cend() ? it->second : nullQIcon(); } void setup() override diff --git a/src/app/theme.h b/src/app/theme.h index 2f9a261c..683d8a24 100644 --- a/src/app/theme.h +++ b/src/app/theme.h @@ -25,12 +25,15 @@ class Theme { ButtonView3d_Background, ButtonView3d_Hover, ButtonView3d_Checked, + Graphic3d_AspectFillArea, View3d_BackgroundGradientStart, View3d_BackgroundGradientEnd, RubberBandView3d_Line, RubberBandView3d_Fill, - MessageIndicator_Background, - MessageIndicator_Text + MessageIndicator_InfoBackground, + MessageIndicator_InfoText, + MessageIndicator_ErrorBackground, + MessageIndicator_ErrorText }; enum class Icon { @@ -56,6 +59,7 @@ class Theme { ZoomIn, ZoomOut, ClipPlane, + Measure, View3dIso, View3dLeft, View3dRight, diff --git a/src/app/widget_clip_planes.cpp b/src/app/widget_clip_planes.cpp index 08ad5290..636842cf 100644 --- a/src/app/widget_clip_planes.cpp +++ b/src/app/widget_clip_planes.cpp @@ -51,45 +51,27 @@ WidgetClipPlanes::WidgetClipPlanes(const Handle_V3d_View& view3d, QWidget* paren } }; - auto fnGetCappingColor = [=](const ClipPlaneData& data) { - if (&data == &m_vecClipPlaneData.at(0)) - return Quantity_NOC_RED1; - else if (&data == &m_vecClipPlaneData.at(1)) - return Quantity_NOC_GREEN1; - else if (&data == &m_vecClipPlaneData.at(2)) - return Quantity_NOC_BLUE1; - else - return Quantity_NOC_GRAY; - }; - - - const auto appModule = AppModule::get(Application::instance()); + const auto appModule = AppModule::get(); for (ClipPlaneData& data : m_vecClipPlaneData) { data.ui.widget_Control->setEnabled(data.ui.check_On->isChecked()); this->connectUi(&data); - data.graphics->SetCapping(appModule->clipPlanesCappingOn.value()); -#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) - data.graphics->SetCappingColor(fnGetCappingColor(data)); -#else - Graphic3d_MaterialAspect cappingMaterial(Graphic3d_NOM_STEEL); - cappingMaterial.SetColor(fnGetCappingColor(data)); - data.graphics->SetCappingMaterial(cappingMaterial); -#endif - if (m_textureCapping && appModule->clipPlanesCappingHatchOn.value()) + data.graphics->SetCapping(appModule->properties()->clipPlanesCappingOn.value()); + data.graphics->SetUseObjectMaterial(true); + if (m_textureCapping && appModule->properties()->clipPlanesCappingHatchOn) data.graphics->SetCappingTexture(m_textureCapping); } - const auto settings = Application::instance()->settings(); + const auto settings = appModule->settings(); QObject::connect(settings, &Settings::changed, this, [=](Property* property) { - if (property == &appModule->clipPlanesCappingOn) { + if (property == &appModule->properties()->clipPlanesCappingOn) { for (ClipPlaneData& data : m_vecClipPlaneData) - data.graphics->SetCapping(appModule->clipPlanesCappingOn.value()); + data.graphics->SetCapping(appModule->properties()->clipPlanesCappingOn); m_view->Redraw(); } - else if (property == &appModule->clipPlanesCappingHatchOn) { + else if (property == &appModule->properties()->clipPlanesCappingHatchOn) { Handle_Graphic3d_TextureMap hatchTexture; - if (m_textureCapping && appModule->clipPlanesCappingHatchOn.value()) + if (m_textureCapping && appModule->properties()->clipPlanesCappingHatchOn) hatchTexture = m_textureCapping; for (ClipPlaneData& data : m_vecClipPlaneData) @@ -164,7 +146,7 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) QSignalBlocker sigBlock(posSlider); Q_UNUSED(sigBlock); const double dPct = ui.spinValueToSliderValue(pos); posSlider->setValue(qRound(dPct)); - GraphicsUtils::Gpx3dClipPlane_setPosition(gfx, pos); + GraphicsUtils::Gfx3dClipPlane_setPosition(gfx, pos); m_view->Redraw(); }); @@ -172,14 +154,14 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) const double pos = ui.sliderValueToSpinValue(pct); QSignalBlocker sigBlock(posSpin); Q_UNUSED(sigBlock); posSpin->setValue(pos); - GraphicsUtils::Gpx3dClipPlane_setPosition(gfx, pos); + GraphicsUtils::Gfx3dClipPlane_setPosition(gfx, pos); m_view->Redraw(); }); QObject::connect(ui.inverseBtn(), &QAbstractButton::clicked, this, [=]{ const gp_Dir invNormal = gfx->ToPlane().Axis().Direction().Reversed(); - GraphicsUtils::Gpx3dClipPlane_setNormal(gfx, invNormal); - GraphicsUtils::Gpx3dClipPlane_setPosition(gfx, data->ui.posSpin()->value()); + GraphicsUtils::Gfx3dClipPlane_setNormal(gfx, invNormal); + GraphicsUtils::Gfx3dClipPlane_setPosition(gfx, data->ui.posSpin()->value()); m_view->Redraw(); }); @@ -197,7 +179,7 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) const gp_Dir normal(vecNormal); const auto bbc = BndBoxCoords::get(m_bndBox); this->setPlaneRange(data, MathUtils::planeRange(bbc, normal)); - GraphicsUtils::Gpx3dClipPlane_setNormal(gfx, normal); + GraphicsUtils::Gfx3dClipPlane_setNormal(gfx, normal); m_view->Redraw(); } }); @@ -242,7 +224,7 @@ void WidgetClipPlanes::setPlaneRange(ClipPlaneData* data, const Range& range) posSpin->setRange(rmin - gap, rmax + gap); posSpin->setSingleStep(std::abs(posSpin->maximum() - posSpin->minimum()) / 100.); if (useMidValue) { - GraphicsUtils::Gpx3dClipPlane_setPosition(data->graphics, newPlanePos); + GraphicsUtils::Gfx3dClipPlane_setPosition(data->graphics, newPlanePos); posSpin->setValue(newPlanePos); posSlider->setValue(data->ui.spinValueToSliderValue(newPlanePos)); } diff --git a/src/app/widget_explode_assembly.ui b/src/app/widget_explode_assembly.ui index 4553dea7..478a9a7d 100644 --- a/src/app/widget_explode_assembly.ui +++ b/src/app/widget_explode_assembly.ui @@ -6,8 +6,8 @@ 0 0 - 148 - 26 + 160 + 30 @@ -15,16 +15,16 @@ - 2 + 4 - 2 + 4 - 2 + 4 - 2 + 4 diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index d1b59111..96db0fc9 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -6,6 +6,7 @@ #include "widget_gui_document.h" +#include "../base/cpp_utils.h" #include "../graphics/graphics_utils.h" #include "../graphics/v3d_view_camera_animation.h" #include "../gui/gui_document.h" @@ -13,6 +14,7 @@ #include "theme.h" #include "widget_clip_planes.h" #include "widget_explode_assembly.h" +#include "widget_measure.h" #include "widget_occ_view.h" #include "widget_occ_view_controller.h" #include "widgets_utils.h" @@ -76,17 +78,29 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) m_controller(new WidgetOccViewController(m_qtOccView)) { { - auto layout = new QVBoxLayout; + auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_qtOccView->widget()); - this->setLayout(layout); } - m_btnFitAll = this->createViewBtn(this, Theme::Icon::Expand, tr("Fit All")); - m_btnEditClipping = this->createViewBtn(this, Theme::Icon::ClipPlane, tr("Edit clip planes")); + auto widgetBtnsContents = new QWidget; + 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_btnEditClipping = this->createViewBtn(widgetBtnsContents, Theme::Icon::ClipPlane, tr("Edit clip planes")); m_btnEditClipping->setCheckable(true); - m_btnExplode = this->createViewBtn(this, Theme::Icon::Multiple, tr("Explode assemblies")); + 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_btnEditClipping); + layoutBtns->addWidget(m_btnExplode); + layoutBtns->addWidget(m_btnMeasure); + m_widgetBtns = this->createWidgetPanelContainer(widgetBtnsContents); auto gfxScene = m_guiDoc->graphicsScene(); QObject::connect(m_btnFitAll, &ButtonFlat::clicked, this, [=]{ @@ -94,16 +108,24 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) }); QObject::connect( m_btnEditClipping, &ButtonFlat::checked, - this, &WidgetGuiDocument::toggleWidgetClipPlanes); + this, &WidgetGuiDocument::toggleWidgetClipPlanes + ); QObject::connect( m_btnExplode, &ButtonFlat::checked, - this, &WidgetGuiDocument::toggleWidgetExplode); + this, &WidgetGuiDocument::toggleWidgetExplode + ); + QObject::connect( + m_btnMeasure, &ButtonFlat::checked, + this, &WidgetGuiDocument::toggleWidgetMeasure + ); QObject::connect( m_controller, &V3dViewController::dynamicActionStarted, - m_guiDoc, &GuiDocument::stopViewCameraAnimation); + m_guiDoc, &GuiDocument::stopViewCameraAnimation + ); QObject::connect( m_controller, &V3dViewController::viewScaled, - m_guiDoc, &GuiDocument::stopViewCameraAnimation); + m_guiDoc, &GuiDocument::stopViewCameraAnimation + ); QObject::connect(m_controller, &V3dViewController::mouseClicked, this, [=](Qt::MouseButton btn) { if (btn == Qt::LeftButton && !m_guiDoc->processAction(gfxScene->currentHighlightedOwner())) { gfxScene->select(); @@ -114,23 +136,18 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) auto mode = on ? GraphicsScene::SelectionMode::Multi : GraphicsScene::SelectionMode::Single; gfxScene->setSelectionMode(mode); }); - QObject::connect( - m_guiDoc, &GuiDocument::viewTrihedronModeChanged, - this, &WidgetGuiDocument::recreateViewControls); m_guiDoc->viewCameraAnimation()->setRenderFunction([=](const Handle_V3d_View& view){ if (view == m_qtOccView->v3dView()) m_qtOccView->redraw(); }); - - this->recreateViewControls(); } QColor WidgetGuiDocument::panelBackgroundColor() const { QColor color = mayoTheme()->color(Theme::Color::Palette_Window); if (m_qtOccView->supportsWidgetOpacity()) - color.setAlpha(150); + color.setAlpha(175); return color; } @@ -141,55 +158,84 @@ void WidgetGuiDocument::resizeEvent(QResizeEvent* event) this->layoutViewControls(); this->layoutWidgetPanel(m_widgetClipPlanes); this->layoutWidgetPanel(m_widgetExplodeAsm); + this->layoutWidgetPanel(m_widgetMeasure); +} + +QWidget* WidgetGuiDocument::createWidgetPanelContainer(QWidget* widgetContents) +{ + auto panel = new Internal::PanelView3d(this); + WidgetsUtils::addContentsWidget(panel, widgetContents); + panel->show(); + panel->adjustSize(); + return panel; +} + +void WidgetGuiDocument::updageWidgetPanelControls(QWidget* panelWidget, ButtonFlat* btnPanel) +{ + this->exclusiveButtonCheck(btnPanel); + if (panelWidget) { + panelWidget->parentWidget()->setVisible(btnPanel->isChecked()); + this->layoutWidgetPanel(panelWidget); + } } void WidgetGuiDocument::toggleWidgetClipPlanes(bool on) { - if (!m_widgetClipPlanes) { - if (on) { - auto panel = new Internal::PanelView3d(this); - auto widget = new WidgetClipPlanes(m_guiDoc->v3dView(), panel); - WidgetsUtils::addContentsWidget(panel, widget); - panel->show(); - panel->adjustSize(); - m_widgetClipPlanes = widget; - QObject::connect( - m_guiDoc, &GuiDocument::graphicsBoundingBoxChanged, - widget, &WidgetClipPlanes::setRanges); - widget->setRanges(m_guiDoc->graphicsBoundingBox()); - } + if (m_widgetClipPlanes) { + m_widgetClipPlanes->setClippingOn(on); } - else { - QWidget* panel = m_widgetClipPlanes->parentWidget(); - panel->setVisible(on); - m_widgetClipPlanes->setClippingOn(panel->isVisible()); + else if (on) { + m_widgetClipPlanes = new WidgetClipPlanes(m_guiDoc->v3dView()); + this->createWidgetPanelContainer(m_widgetClipPlanes); + QObject::connect( + m_guiDoc, &GuiDocument::graphicsBoundingBoxChanged, + m_widgetClipPlanes, &WidgetClipPlanes::setRanges + ); + m_widgetClipPlanes->setRanges(m_guiDoc->graphicsBoundingBox()); } - this->layoutWidgetPanel(m_widgetClipPlanes); - if (on) - m_btnExplode->setChecked(false); + this->updageWidgetPanelControls(m_widgetClipPlanes, m_btnEditClipping); } void WidgetGuiDocument::toggleWidgetExplode(bool on) { - if (!m_widgetExplodeAsm) { - if (on) { - auto panel = new Internal::PanelView3d(this); - auto widget = new WidgetExplodeAssembly(m_guiDoc, panel); - WidgetsUtils::addContentsWidget(panel, widget); - panel->show(); - panel->adjustSize(); - m_widgetExplodeAsm = widget; - } + if (!m_widgetExplodeAsm && on) { + m_widgetExplodeAsm = new WidgetExplodeAssembly(m_guiDoc); + this->createWidgetPanelContainer(m_widgetExplodeAsm); } - else { - QWidget* panel = m_widgetExplodeAsm->parentWidget(); - panel->setVisible(on); + + this->updageWidgetPanelControls(m_widgetExplodeAsm, m_btnExplode); +} + +void WidgetGuiDocument::toggleWidgetMeasure(bool on) +{ + if (!m_widgetMeasure && on) { + m_widgetMeasure = new WidgetMeasure(m_guiDoc); + auto container = this->createWidgetPanelContainer(m_widgetMeasure); + QObject::connect( + m_widgetMeasure, &WidgetMeasure::sizeAdjustmentRequested, + container, &QWidget::adjustSize, + Qt::QueuedConnection + ); } - this->layoutWidgetPanel(m_widgetExplodeAsm); - if (on) - m_btnEditClipping->setChecked(false); + if (m_widgetMeasure) + m_widgetMeasure->setMeasureOn(on); + + this->updageWidgetPanelControls(m_widgetMeasure, m_btnMeasure); +} + +void WidgetGuiDocument::exclusiveButtonCheck(ButtonFlat* btnCheck) +{ + if (!btnCheck || !btnCheck->isChecked()) + return; + + ButtonFlat* arrayToggleBtn[] = { m_btnEditClipping, m_btnExplode, m_btnMeasure }; + for (ButtonFlat* btn : arrayToggleBtn) { + assert(btn->isCheckable()); + if (btn != btnCheck) + btn->setChecked(false); + } } void WidgetGuiDocument::layoutWidgetPanel(QWidget* panel) @@ -231,13 +277,13 @@ ButtonFlat* WidgetGuiDocument::createViewBtn(QWidget* parent, Theme::Icon icon, btn->setCheckedBrush(mayoTheme()->color(Theme::Color::ButtonView3d_Checked)); btn->setHoverBrush(mayoTheme()->color(Theme::Color::ButtonView3d_Hover)); btn->setIcon(mayoTheme()->icon(icon)); - btn->setIconSize(QSize(18, 18)); - btn->setFixedSize(24, 24); + btn->setIconSize(QSize(20, 20)); + btn->setFixedSize(28, 28); btn->setToolTip(tooltip); return btn; } -void WidgetGuiDocument::recreateViewControls() +void WidgetGuiDocument::recreateMenuViewProjections(QWidget* container) { for (QWidget* widget : m_vecWidgetForViewProj) delete widget; @@ -268,7 +314,8 @@ void WidgetGuiDocument::recreateViewControls() const QString strTemplateTooltip = tr("Left-click: popup menu of pre-defined views\n" "CTRL+Left-click: apply '%1' view"); - auto btnViewMenu = this->createViewBtn(this, Theme::Icon::View3dIso, QString()); + auto btnViewMenu = this->createViewBtn(container, Theme::Icon::View3dIso, QString()); + container->layout()->addWidget(btnViewMenu); btnViewMenu->setToolTip(strTemplateTooltip.arg(btnCreationData[0].text)); btnViewMenu->setData(static_cast(btnCreationData[0].proj)); auto menuBtnView = new QMenu(btnViewMenu); @@ -295,46 +342,37 @@ void WidgetGuiDocument::recreateViewControls() QObject::connect(btnViewMenu, &ButtonFlat::clicked, this, [=]{ const Qt::KeyboardModifiers keyMods = QGuiApplication::queryKeyboardModifiers(); if (!keyMods.testFlag(Qt::ControlModifier)) - menuBtnView->popup(btnViewMenu->mapToGlobal({ 0, btnViewMenu->height() })); + menuBtnView->popup(btnViewMenu->mapToGlobal(QPoint{ 0, container->height() })); else m_guiDoc->setViewCameraOrientation(V3d_TypeOfOrientation(btnViewMenu->data().toInt())); }); } else { for (const ButtonCreationData& btnData : btnCreationData) { - auto btnViewProj = this->createViewBtn(this, btnData.icon, btnData.text); + auto btnViewProj = this->createViewBtn(container, btnData.icon, btnData.text); + container->layout()->addWidget(btnViewProj); QObject::connect(btnViewProj, &ButtonFlat::clicked, this, [=]{ m_guiDoc->setViewCameraOrientation(btnData.proj); }); m_vecWidgetForViewProj.push_back(btnViewProj); } } - - this->layoutViewControls(); } QRect WidgetGuiDocument::viewControlsRect() const { - const QRect rectFirstBtn = m_btnFitAll->frameGeometry(); - const QRect rectLastBtn = m_btnExplode->frameGeometry(); - QRect rect; - rect.setCoords( - rectFirstBtn.left(), rectFirstBtn.top(), - rectLastBtn.right(), rectLastBtn.bottom()); - return rect; + return m_widgetBtns->frameGeometry(); } void WidgetGuiDocument::layoutViewControls() { - const int margin = Internal::widgetMargin; + const int margin = Internal::widgetMargin + 2; auto fnGetViewControlsPos = [=]() -> QPoint { if (m_guiDoc->viewTrihedronMode() == GuiDocument::ViewTrihedronMode::AisViewCube) { const int btnSize = m_btnFitAll->width(); const int viewCubeBndSize = m_guiDoc->aisViewCubeBoundingSize(); - const int ctrlCount = 2 + m_vecWidgetForViewProj.size(); - const int ctrlWidth = ctrlCount * btnSize + (ctrlCount - 1) * margin; const int ctrlHeight = btnSize; - const int ctrlXOffset = (viewCubeBndSize - ctrlWidth) / 2; + const int ctrlXOffset = margin; switch (m_guiDoc->viewTrihedronCorner()) { case Qt::TopLeftCorner: return { ctrlXOffset, viewCubeBndSize + margin }; @@ -351,15 +389,7 @@ void WidgetGuiDocument::layoutViewControls() return { margin, margin }; }; - m_btnFitAll->move(fnGetViewControlsPos()); - const QWidget* widgetLast = m_btnFitAll; - for (QWidget* widget : m_vecWidgetForViewProj) { - WidgetsUtils::moveWidgetRightTo(widget, widgetLast, margin); - widgetLast = widget; - } - - WidgetsUtils::moveWidgetRightTo(m_btnEditClipping, widgetLast, margin); - WidgetsUtils::moveWidgetRightTo(m_btnExplode, m_btnEditClipping, margin); + m_widgetBtns->move(fnGetViewControlsPos()); } } // namespace Mayo diff --git a/src/app/widget_gui_document.h b/src/app/widget_gui_document.h index c1d2e5c0..bdb63718 100644 --- a/src/app/widget_gui_document.h +++ b/src/app/widget_gui_document.h @@ -19,6 +19,7 @@ class ButtonFlat; class GuiDocument; class WidgetClipPlanes; class WidgetExplodeAssembly; +class WidgetMeasure; class IWidgetOccView; class WidgetGuiDocument : public QWidget { @@ -27,7 +28,7 @@ class WidgetGuiDocument : public QWidget { WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent = nullptr); GuiDocument* guiDocument() const { return m_guiDoc; } - V3dViewController* controller() const { return m_controller; } + WidgetOccViewController* controller() const { return m_controller; } IWidgetOccView* view() const { return m_qtOccView; } QColor panelBackgroundColor() const; @@ -36,10 +37,15 @@ class WidgetGuiDocument : public QWidget { void resizeEvent(QResizeEvent* event) override; private: + QWidget* createWidgetPanelContainer(QWidget* widgetContents); + void updageWidgetPanelControls(QWidget* panelWidget, ButtonFlat* btnPanel); + void toggleWidgetClipPlanes(bool on); void toggleWidgetExplode(bool on); + void toggleWidgetMeasure(bool on); + void exclusiveButtonCheck(ButtonFlat* btn); - void recreateViewControls(); + void recreateMenuViewProjections(QWidget* container); QRect viewControlsRect() const; void layoutViewControls(); void layoutWidgetPanel(QWidget* panel); @@ -48,14 +54,17 @@ class WidgetGuiDocument : public QWidget { GuiDocument* m_guiDoc = nullptr; IWidgetOccView* m_qtOccView = nullptr; + QWidget* m_widgetBtns = nullptr; WidgetOccViewController* m_controller = nullptr; WidgetClipPlanes* m_widgetClipPlanes = nullptr; WidgetExplodeAssembly* m_widgetExplodeAsm = nullptr; + WidgetMeasure* m_widgetMeasure = nullptr; QRect m_rectControls; ButtonFlat* m_btnFitAll = nullptr; ButtonFlat* m_btnEditClipping = nullptr; ButtonFlat* m_btnExplode = nullptr; + ButtonFlat* m_btnMeasure = nullptr; std::vector m_vecWidgetForViewProj; }; diff --git a/src/app/widget_home_files.cpp b/src/app/widget_home_files.cpp index 36b9ee2c..4a8d0015 100644 --- a/src/app/widget_home_files.cpp +++ b/src/app/widget_home_files.cpp @@ -84,8 +84,7 @@ class HomeFilesModel : public ListHelper::Model { pixmap = fnPixmap(mayoTheme()->icon(Theme::Icon::OpenFiles), 128, 96); } else { - auto appModule = AppModule::get(Application::instance()); - const RecentFile* recentFile = appModule ? appModule->findRecentFile(filepathFrom(url)) : nullptr; + const RecentFile* recentFile = AppModule::get()->findRecentFile(filepathFrom(url)); pixmap = recentFile ? recentFile->thumbnail : QPixmap(); if (pixmap.isNull()) { const QIcon icon = m_fileIconProvider.icon(QFileInfo(url)); @@ -110,9 +109,8 @@ class HomeFilesModel : public ListHelper::Model { private: void reloadRecentFiles() { - auto app = Application::instance(); - auto appModule = AppModule::get(app); - const RecentFiles& listRecentFile = appModule->recentFiles.value(); + auto appModule = AppModule::get(); + const RecentFiles& listRecentFile = appModule->properties()->recentFiles.value(); if (m_cacheRecentFiles == listRecentFile) return; @@ -136,7 +134,6 @@ class HomeFilesModel : public ListHelper::Model { return WidgetHomeFiles::tr("%1 days ago %2").arg(diffDays).arg(strTime); } else { - auto appModule = AppModule::get(Application::instance()); const QString strDate = appModule->locale().toString(date, QLocale::ShortFormat); return WidgetHomeFiles::tr("%1 %2").arg(strDate, strTime); } @@ -155,7 +152,7 @@ class HomeFilesModel : public ListHelper::Model { "Modified: %4\n" "Read: %5\n") .arg(QDir::toNativeSeparators(fi.absolutePath())) - .arg(QStringUtils::bytesText(fi.size(), AppModule::get(Application::instance())->locale())) + .arg(QStringUtils::bytesText(fi.size(), appModule->locale())) .arg(fnToString(fi.birthTime())) .arg(fnToString(fi.lastModified())) .arg(fnToString(fi.lastRead())) @@ -208,8 +205,7 @@ class HomeFilesDelegate : public ListHelper::ItemDelegate { WidgetHomeFiles::WidgetHomeFiles(QWidget* parent) : QWidget(parent) { - auto app = Application::instance(); - auto appModule = AppModule::get(app); + auto appModule = AppModule::get(); auto layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); @@ -226,8 +222,8 @@ WidgetHomeFiles::WidgetHomeFiles(QWidget* parent) m_gridDelegate->setItemPixmapSize(appModule->recentFileThumbnailSize()); m_gridView->setItemDelegate(m_gridDelegate); - QObject::connect(app->settings(), &Settings::changed, this, [=](const Property* setting) { - if (setting == &appModule->recentFiles) + QObject::connect(appModule->settings(), &Settings::changed, this, [=](const Property* setting) { + if (setting == &appModule->properties()->recentFiles) model->reload(); }); } diff --git a/src/app/widget_measure.cpp b/src/app/widget_measure.cpp new file mode 100644 index 00000000..1ec7abc9 --- /dev/null +++ b/src/app/widget_measure.cpp @@ -0,0 +1,446 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "widget_measure.h" +#include "measure_display.h" +#include "measure_tool_brep.h" +#include "qstring_conv.h" +#include "theme.h" +#include "ui_widget_measure.h" + +#include "../base/unit_system.h" +#include "../gui/gui_document.h" + +#include +#include + +#include +#include + +namespace Mayo { + +namespace { + +using IMeasureToolPtr = std::unique_ptr; + +// Get global array of available measurement tool objects +std::vector& getMeasureTools() +{ + static std::vector vecTool; + return vecTool; +} + +// Returns the tool object adapted for the graphics object 'gfxObject' and measure type +// Note: the search is performed in the array returned by getMeasureTools() function +IMeasureTool* findSupportingMeasureTool(const GraphicsObjectPtr& gfxObject, MeasureType measureType) +{ + for (const IMeasureToolPtr& ptr : getMeasureTools()) { + if (ptr->supports(measureType) && ptr->supports(gfxObject)) + return ptr.get(); + } + + return nullptr; +} + +// Helper function to iterate and execute function 'fn' on all the graphics objects owned by a +// measure display +template +void foreachGraphicsObject(const IMeasureDisplay* ptr, FUNCTION fn) +{ + if (ptr) { + const int count = ptr->graphicsObjectsCount(); + for (int i = 0; i < count; ++i) + fn(ptr->graphicsObjectAt(i)); + } +} + +// Overload of the helper function above +template +void foreachGraphicsObject(const std::unique_ptr& ptr, FUNCTION fn) { + foreachGraphicsObject(ptr.get(), fn); +} + +} // namespace + +WidgetMeasure::WidgetMeasure(GuiDocument* guiDoc, QWidget* parent) + : QWidget(parent), + m_ui(new Ui_WidgetMeasure), + m_guiDoc(guiDoc) +{ + if (getMeasureTools().empty()) + getMeasureTools().push_back(std::make_unique()); + + m_ui->setupUi(this); + QObject::connect( + m_ui->combo_MeasureType, qOverload(&QComboBox::currentIndexChanged), + this, &WidgetMeasure::onMeasureTypeChanged + ); + QObject::connect( + m_ui->combo_LengthUnit, qOverload(&QComboBox::currentIndexChanged), + this, &WidgetMeasure::onMeasureUnitsChanged + ); + QObject::connect( + m_ui->combo_AngleUnit, qOverload(&QComboBox::currentIndexChanged), + this, &WidgetMeasure::onMeasureUnitsChanged + ); + QObject::connect( + m_ui->combo_AreaUnit, qOverload(&QComboBox::currentIndexChanged), + this, &WidgetMeasure::onMeasureUnitsChanged + ); + + this->onMeasureTypeChanged(m_ui->combo_MeasureType->currentIndex()); + this->updateMessagePanel(); +} + +WidgetMeasure::~WidgetMeasure() +{ + delete m_ui; +} + +void WidgetMeasure::setMeasureOn(bool on) +{ + m_errorMessage.clear(); + auto gfxScene = m_guiDoc->graphicsScene(); + if (on) { + this->onMeasureTypeChanged(m_ui->combo_MeasureType->currentIndex()); + m_connGraphicsSelectionChanged = QObject::connect( + gfxScene, &GraphicsScene::selectionChanged, + this, &WidgetMeasure::onGraphicsSelectionChanged + ); + } + else { + gfxScene->foreachDisplayedObject([=](const GraphicsObjectPtr& gfxObject) { + gfxScene->deactivateObjectSelection(gfxObject); + gfxScene->activateObjectSelection(gfxObject, 0); + }); + gfxScene->clearSelection(); + QObject::disconnect(m_connGraphicsSelectionChanged); + } +} + +void WidgetMeasure::addTool(std::unique_ptr tool) +{ + if (tool) + getMeasureTools().push_back(std::move(tool)); +} + +MeasureType WidgetMeasure::toMeasureType(int comboBoxId) +{ + switch (comboBoxId) { + case 0: return MeasureType::VertexPosition; + 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; + } + return MeasureType::None; +} + +LengthUnit WidgetMeasure::toMeasureLengthUnit(int comboBoxId) +{ + switch (comboBoxId) { + case 0: return LengthUnit::Millimeter; + case 1: return LengthUnit::Centimeter; + case 2: return LengthUnit::Meter; + case 3: return LengthUnit::Inch; + case 4: return LengthUnit::Foot; + case 5: return LengthUnit::Yard; + } + return {}; +} + +AngleUnit WidgetMeasure::toMeasureAngleUnit(int comboBoxId) +{ + switch (comboBoxId) { + case 0: return AngleUnit::Degree; + case 1: return AngleUnit::Radian; + } + return {}; +} + +AreaUnit WidgetMeasure::toMeasureAreaUnit(int comboBoxId) +{ + switch (comboBoxId) { + case 0: return AreaUnit::SquareMillimeter; + case 1: return AreaUnit::SquareCentimeter; + case 2: return AreaUnit::SquareMeter; + case 3: return AreaUnit::SquareInch; + case 4: return AreaUnit::SquareFoot; + case 5: return AreaUnit::SquareYard; + } + return {}; +} + +void WidgetMeasure::onMeasureTypeChanged(int id) +{ + // Update widgets visibility + const MeasureType measureType = WidgetMeasure::toMeasureType(id); + const bool measureIsLengthBased = measureType != MeasureType::Angle; + const bool measureIsAngle = measureType == MeasureType::Angle; + const bool measureIsArea = measureType == MeasureType::Area; + m_ui->label_LengthUnit->setVisible(measureIsLengthBased && !measureIsArea); + m_ui->combo_LengthUnit->setVisible(measureIsLengthBased && !measureIsArea); + m_ui->label_AngleUnit->setVisible(measureIsAngle); + m_ui->combo_AngleUnit->setVisible(measureIsAngle); + m_ui->label_AreaUnit->setVisible(measureIsArea); + m_ui->combo_AreaUnit->setVisible(measureIsArea); + + auto gfxScene = m_guiDoc->graphicsScene(); + + // Find measure tool + m_tool = nullptr; + gfxScene->foreachDisplayedObject([=](const GraphicsObjectPtr& gfxObject) { + if (!m_tool) + m_tool = findSupportingMeasureTool(gfxObject, measureType); + }); + + // Apply 3D selection modes required by the measure tool + gfxScene->clearSelection(); + gfxScene->foreachDisplayedObject([=](const GraphicsObjectPtr& gfxObject) { + if (GuiDocument::isAisViewCubeObject(gfxObject)) + return; // Skip + + gfxScene->deactivateObjectSelection(gfxObject); + if (m_tool) { + for (GraphicsObjectSelectionMode mode : m_tool->selectionModes(measureType)) + gfxScene->activateObjectSelection(gfxObject, mode); + } + }); + gfxScene->redraw(); +} + +void WidgetMeasure::onMeasureUnitsChanged() +{ + const MeasureConfig config = this->currentMeasureConfig(); + for (IMeasureDisplayPtr& measure : m_vecMeasureDisplay) { + measure->update(config); + foreachGraphicsObject(measure, [](const GraphicsObjectPtr& gfxObject) { + gfxObject->Redisplay(); + }); + } + + m_guiDoc->graphicsScene()->redraw(); + this->updateMessagePanel(); +} + +MeasureType WidgetMeasure::currentMeasureType() const +{ + return WidgetMeasure::toMeasureType(m_ui->combo_MeasureType->currentIndex()); +} + +MeasureConfig WidgetMeasure::currentMeasureConfig() const +{ + return { + WidgetMeasure::toMeasureLengthUnit(m_ui->combo_LengthUnit->currentIndex()), + WidgetMeasure::toMeasureAngleUnit(m_ui->combo_AngleUnit->currentIndex()), + WidgetMeasure::toMeasureAreaUnit(m_ui->combo_AreaUnit->currentIndex()) + }; +} + +void WidgetMeasure::onGraphicsSelectionChanged() +{ + auto gfxScene = m_guiDoc->graphicsScene(); + std::vector vecNewSelected; + std::vector vecDeselected; + { + // Store currently selected graphics + std::vector vecSelected; + gfxScene->foreachSelectedOwner([&](const GraphicsOwnerPtr& owner) { + vecSelected.push_back(owner); + }); + + // Find new selected graphics + for (const GraphicsOwnerPtr& owner : vecSelected) { + auto itFound = std::find(m_vecSelectedOwner.begin(), m_vecSelectedOwner.end(), owner); + if (itFound == m_vecSelectedOwner.end()) + vecNewSelected.push_back(owner); + } + + // Find deselected graphics + for (const GraphicsOwnerPtr& owner : m_vecSelectedOwner) { + auto itFound = std::find(vecSelected.begin(), vecSelected.end(), owner); + if (itFound == vecSelected.end()) + vecDeselected.push_back(owner); + } + + m_vecSelectedOwner = std::move(vecSelected); + } + + // Erase objects associated to deselected graphics + for (const GraphicsOwnerPtr& owner : vecDeselected) { + for (auto link = this->findLink(owner); link != nullptr; link = this->findLink(owner)) { + this->eraseMeasureDisplay(link->measureDisplay); + this->eraseLink(link); + } + } + + m_guiDoc->graphicsScene()->redraw(); + m_errorMessage.clear(); + + // Exit if no measure tool available + if (!m_tool) + return; + + // Holds MeasureDisplay objects created for new selected graphics + std::vector vecNewMeasureDisplay; + + // Helper to ease addition(registration) of MeasureDisplay object + auto fnAddMeasureDisplay = [&]( + std::unique_ptr ptr, + std::initializer_list listOwner) + { + if (ptr) { + vecNewMeasureDisplay.push_back(std::move(ptr)); + for (const GraphicsOwnerPtr& owner : listOwner) + this->addLink(owner, vecNewMeasureDisplay.back()); + } + }; + + const MeasureType measureType = this->currentMeasureType(); + // Create MeasureDisplay objects needing a newly single selected graphics object + for (const GraphicsOwnerPtr& owner : vecNewSelected) { + try { + const MeasureValue value = IMeasureTool_computeValue(*m_tool, measureType, owner); + if (MeasureValue_isValid(value)) + fnAddMeasureDisplay(BaseMeasureDisplay::createFrom(measureType, value), { owner }); + } catch (const IMeasureError& err) { + m_errorMessage = to_QString(err.message()); + } + } + + // Create MeasureDisplay objects needing currently two selected graphics objects + if (m_vecSelectedOwner.size() == 2) { + const GraphicsOwnerPtr& owner1 = m_vecSelectedOwner.front(); + const GraphicsOwnerPtr& owner2 = m_vecSelectedOwner.back(); + try { + const MeasureValue value = IMeasureTool_computeValue(*m_tool, measureType, owner1, owner2); + if (MeasureValue_isValid(value)) + fnAddMeasureDisplay(BaseMeasureDisplay::createFrom(measureType, value), { owner1, owner2 }); + } catch (const IMeasureError& err) { + m_errorMessage = to_QString(err.message()); + } + } + + // Display new measure graphics objects + for (IMeasureDisplayPtr& measure : vecNewMeasureDisplay) { + measure->update(this->currentMeasureConfig()); + foreachGraphicsObject(measure, [=](const GraphicsObjectPtr& gfxObject) { + gfxObject->SetZLayer(Graphic3d_ZLayerId_Topmost); + gfxScene->addObject(gfxObject); + }); + + m_vecMeasureDisplay.push_back(std::move(measure)); + } + + this->updateMessagePanel(); +} + +void WidgetMeasure::updateMessagePanel() +{ + // Clear message panel + while (m_ui->layout_Message->count() > 0) { + QLayoutItem* item = m_ui->layout_Message->takeAt(m_ui->layout_Message->count() - 1); + delete item->widget(); + delete item; + } + + // Error message takes precedence + if (!m_errorMessage.isEmpty() || m_vecMeasureDisplay.empty()) { + auto labelMessage = new QLabel(m_ui->widget_Message); + labelMessage->setContentsMargins(m_ui->layout_Main->contentsMargins()); + m_ui->layout_Message->addWidget(labelMessage); + const auto msgTextColorRole = + m_errorMessage.isEmpty() ? + Theme::Color::MessageIndicator_InfoText : + Theme::Color::MessageIndicator_ErrorText; + const auto msgBackgroundColorRole = + m_errorMessage.isEmpty() ? + Theme::Color::MessageIndicator_InfoBackground : + Theme::Color::MessageIndicator_ErrorBackground; + labelMessage->setStyleSheet( + QString("QLabel { color: %1; background-color: %2 }") + .arg(mayoTheme()->color(msgTextColorRole).name(), + mayoTheme()->color(msgBackgroundColorRole).name()) + ); + const QString msg = m_errorMessage.isEmpty() ? tr("Select entities to measure") : m_errorMessage; + labelMessage->setText(msg); + } + else { + // The font to be used for measure results + const QFont fontResult = QFontDatabase::systemFont(QFontDatabase::FixedFont); + // Helper function to add the text of an IMeasureDisplay object in the message panel + auto fnAddMeasureText = [=](const IMeasureDisplayPtr& measure) { + auto label = new QLabel(to_QString(measure->text()), m_ui->widget_Message); + label->setFont(fontResult); + m_ui->layout_Message->addWidget(label); + }; + + // Add measure texts to the message panel + for (const IMeasureDisplayPtr& measure : m_vecMeasureDisplay) + fnAddMeasureText(measure); + + // Handle the case where there are many measures and sum is a supported operation + if (m_vecMeasureDisplay.size() > 1) { + auto sumMeasure = BaseMeasureDisplay::createEmptySumFrom(this->currentMeasureType()); + if (sumMeasure && sumMeasure->isSumSupported()) { + for (const IMeasureDisplayPtr& measure : m_vecMeasureDisplay) + sumMeasure->sumAdd(*measure); + + sumMeasure->update(this->currentMeasureConfig()); + fnAddMeasureText(sumMeasure); + } + } + } + + emit this->sizeAdjustmentRequested(); +} + +void WidgetMeasure::eraseMeasureDisplay(const IMeasureDisplay* measure) +{ + if (!measure) + return; + + auto it = std::find_if( + m_vecMeasureDisplay.begin(), + m_vecMeasureDisplay.end(), + [=](const IMeasureDisplayPtr& ptr) { return ptr.get() == measure; } + ); + if (it != m_vecMeasureDisplay.end()) { + foreachGraphicsObject(measure, [=](const GraphicsObjectPtr& gfxObject) { + m_guiDoc->graphicsScene()->eraseObject(gfxObject); + }); + + m_vecMeasureDisplay.erase(it); + } +} + +void WidgetMeasure::addLink(const GraphicsOwnerPtr& owner, const IMeasureDisplayPtr& measure) +{ + if (owner && measure) + m_vecLinkGfxOwnerMeasure.push_back({ owner, measure.get() }); +} + +void WidgetMeasure::eraseLink(const GraphicsOwner_MeasureDisplay* link) +{ + if (!link) + return; + + m_vecLinkGfxOwnerMeasure.erase(m_vecLinkGfxOwnerMeasure.begin() + (link - &m_vecLinkGfxOwnerMeasure.front())); +} + +const WidgetMeasure::GraphicsOwner_MeasureDisplay* WidgetMeasure::findLink(const GraphicsOwnerPtr& owner) const +{ + auto itFound = std::find_if( + m_vecLinkGfxOwnerMeasure.begin(), + m_vecLinkGfxOwnerMeasure.end(), + [=](const GraphicsOwner_MeasureDisplay& link) { return link.gfxOwner == owner; } + ); + return itFound != m_vecLinkGfxOwnerMeasure.end() ? &(*itFound) : nullptr; +} + +} // namespace Mayo diff --git a/src/app/widget_measure.h b/src/app/widget_measure.h new file mode 100644 index 00000000..464a1dc2 --- /dev/null +++ b/src/app/widget_measure.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "measure_display.h" +#include "measure_tool.h" + +#include +#include +#include +#include +#include + +namespace Mayo { + +class GuiDocument; + +class WidgetMeasure : public QWidget { + Q_OBJECT +public: + WidgetMeasure(GuiDocument* guiDoc, QWidget* parent = nullptr); + ~WidgetMeasure(); + + void setMeasureOn(bool on); + + static void addTool(std::unique_ptr tool); + +signals: + void sizeAdjustmentRequested(); + +private: + void onMeasureTypeChanged(int id); + void onMeasureUnitsChanged(); + + static MeasureType toMeasureType(int comboBoxId); + static LengthUnit toMeasureLengthUnit(int comboBoxId); + static AngleUnit toMeasureAngleUnit(int comboBoxId); + static AreaUnit toMeasureAreaUnit(int comboBoxId); + + MeasureType currentMeasureType() const; + MeasureConfig currentMeasureConfig() const; + + void onGraphicsSelectionChanged(); + + void updateMessagePanel(); + + using IMeasureDisplayPtr = std::unique_ptr; + void eraseMeasureDisplay(const IMeasureDisplay* measure); + + // Provides link between GraphicsOwner and IMeasureDisplay object + struct GraphicsOwner_MeasureDisplay { + GraphicsOwnerPtr gfxOwner; + const IMeasureDisplay* measureDisplay = nullptr; + }; + void addLink(const GraphicsOwnerPtr& owner, const IMeasureDisplayPtr& measure); + void eraseLink(const GraphicsOwner_MeasureDisplay* link); + const GraphicsOwner_MeasureDisplay* findLink(const GraphicsOwnerPtr& owner) const; + + // -- Attributes + class Ui_WidgetMeasure* m_ui = nullptr; + GuiDocument* m_guiDoc = nullptr; + std::vector m_vecSelectedOwner; + std::vector m_vecMeasureDisplay; + std::vector m_vecLinkGfxOwnerMeasure; + IMeasureTool* m_tool = nullptr; + QString m_errorMessage; + QMetaObject::Connection m_connGraphicsSelectionChanged; +}; + +} // namespace Mayo diff --git a/src/app/widget_measure.ui b/src/app/widget_measure.ui new file mode 100644 index 00000000..8c06c05f --- /dev/null +++ b/src/app/widget_measure.ui @@ -0,0 +1,203 @@ + + + Mayo::WidgetMeasure + + + + 0 + 0 + 229 + 156 + + + + Form + + + + 4 + + + 4 + + + 4 + + + 4 + + + + + Area Unit + + + + + + + Length Unit + + + + + + + Measure + + + + + + + Angle Unit + + + + + + + + Millimeter(mm) + + + + + Centimeter(cm) + + + + + Meter(m) + + + + + Inch(in) + + + + + Foot(ft) + + + + + Yard(yd) + + + + + + + + + Degree(°) + + + + + Radian(rad) + + + + + + + + + Vertex Position + + + + + Circle Center + + + + + Circle Diameter + + + + + Min Distance + + + + + Angle + + + + + Length + + + + + Surface Area + + + + + + + + + Square Millimeter(mm²) + + + + + Square Centimeter(cm²) + + + + + Square Meter(m²) + + + + + Square Inch(in²) + + + + + Square Foot(ft²) + + + + + Square Yard(yd²) + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + + diff --git a/src/app/widget_message_indicator.cpp b/src/app/widget_message_indicator.cpp index b4beb17f..3d476625 100644 --- a/src/app/widget_message_indicator.cpp +++ b/src/app/widget_message_indicator.cpp @@ -55,11 +55,11 @@ void WidgetMessageIndicator::paintEvent(QPaintEvent*) const QRectF& msgRect = m_messageRect; const QRectF boxRect(0, 0, msgRect.width() + 18, msgRect.height() + 8); - p.fillRect(boxRect, mayoTheme()->color(Theme::Color::MessageIndicator_Background)); + p.fillRect(boxRect, m_backgroundColor); p.setFont(QtGuiUtils::FontChange(this->font()).bold()); const QRectF textRect(9, 4, msgRect.width() + 4, msgRect.height()); - p.setPen(mayoTheme()->color(Theme::Color::MessageIndicator_Text)); + p.setPen(m_textColor); p.drawText(textRect, m_message); } @@ -69,15 +69,24 @@ void WidgetMessageIndicator::runInternal() anim->setDuration(200); anim->setStartValue(1.); anim->setEndValue(0.); - QObject::connect( - anim, &QAbstractAnimation::finished, - this, &QObject::deleteLater); + QObject::connect(anim, &QAbstractAnimation::finished, this, &QObject::deleteLater); anim->start(QAbstractAnimation::DeleteWhenStopped); } -void WidgetMessageIndicator::showMessage(const QString& msg, QWidget* parent) +void WidgetMessageIndicator::showInfo(const QString& msg, QWidget* parent) { - (new WidgetMessageIndicator(msg, parent))->run(); + auto widget = new WidgetMessageIndicator(msg, parent); + widget->setTextColor(mayoTheme()->color(Theme::Color::MessageIndicator_InfoText)); + widget->setBackgroundColor(mayoTheme()->color(Theme::Color::MessageIndicator_InfoBackground)); + widget->run(); +} + +void WidgetMessageIndicator::showError(const QString &msg, QWidget *parent) +{ + auto widget = new WidgetMessageIndicator(msg, parent); + widget->setTextColor(mayoTheme()->color(Theme::Color::MessageIndicator_ErrorText)); + widget->setBackgroundColor(mayoTheme()->color(Theme::Color::MessageIndicator_ErrorBackground)); + widget->run(); } bool WidgetMessageIndicator::eventFilter(QObject *watched, QEvent *event) diff --git a/src/app/widget_message_indicator.h b/src/app/widget_message_indicator.h index 88b0f479..15758b2f 100644 --- a/src/app/widget_message_indicator.h +++ b/src/app/widget_message_indicator.h @@ -23,12 +23,16 @@ class WidgetMessageIndicator : public QWidget { public: WidgetMessageIndicator(const QString& msg, QWidget* parent = nullptr); + void setTextColor(const QColor& color) { m_textColor = color; } + void setBackgroundColor(const QColor& color) { m_backgroundColor = color; } + qreal opacity() const; void setOpacity(qreal value); void run(); - static void showMessage(const QString& msg, QWidget* parent); + static void showInfo(const QString& msg, QWidget* parent); + static void showError(const QString& msg, QWidget* parent); bool eventFilter(QObject* watched, QEvent* event) override; @@ -41,6 +45,8 @@ class WidgetMessageIndicator : public QWidget { const QString m_message; QRectF m_messageRect; qreal m_opacity = 1.; + QColor m_textColor = Qt::black; + QColor m_backgroundColor = Qt::white; }; } // namespace Mayo diff --git a/src/app/widget_model_tree.cpp b/src/app/widget_model_tree.cpp index 7bdfb7a9..de5380f4 100644 --- a/src/app/widget_model_tree.cpp +++ b/src/app/widget_model_tree.cpp @@ -194,9 +194,6 @@ void WidgetModelTree::registerGuiApplication(GuiApplication* guiApp) return; m_guiApp = guiApp; - for (const BuilderPtr& builder : m_vecBuilder) - builder->registerGuiApplication(guiApp); - auto app = guiApp->application(); QObject::connect( app.get(), &Application::documentAdded, diff --git a/src/app/widget_model_tree_builder.h b/src/app/widget_model_tree_builder.h index c591f7e3..66f592ef 100644 --- a/src/app/widget_model_tree_builder.h +++ b/src/app/widget_model_tree_builder.h @@ -36,8 +36,6 @@ class WidgetModelTreeBuilder { QTreeWidget* treeWidget() const { return m_treeWidget; } void setTreeWidget(QTreeWidget* tree) { m_treeWidget = tree; } - virtual void registerGuiApplication(GuiApplication* /*guiApp*/) {} - virtual WidgetModelTree_UserActions createUserActions(QObject* /*parent*/) { return {}; } virtual std::unique_ptr clone() const; diff --git a/src/app/widget_model_tree_builder_mesh.cpp b/src/app/widget_model_tree_builder_mesh.cpp index a4487bac..d88c0ec8 100644 --- a/src/app/widget_model_tree_builder_mesh.cpp +++ b/src/app/widget_model_tree_builder_mesh.cpp @@ -6,17 +6,17 @@ #include "widget_model_tree_builder_mesh.h" #include "../base/caf_utils.h" +#include "../base/data_triangulation.h" #include "theme.h" #include "widget_model_tree.h" #include -#include namespace Mayo { bool WidgetModelTreeBuilder_Mesh::supportsDocumentTreeNode(const DocumentTreeNode& node) const { - return CafUtils::hasAttribute(node.label()); + return CafUtils::hasAttribute(node.label()); } QTreeWidgetItem* WidgetModelTreeBuilder_Mesh::createTreeItem(const DocumentTreeNode& node) diff --git a/src/app/widget_model_tree_builder_xde.cpp b/src/app/widget_model_tree_builder_xde.cpp index fd8282f3..7006fbb1 100644 --- a/src/app/widget_model_tree_builder_xde.cpp +++ b/src/app/widget_model_tree_builder_xde.cpp @@ -13,7 +13,7 @@ #include "theme.h" #include "widget_model_tree.h" -#include +#include // WARNING Qt5 / Qt6 #include #include @@ -23,23 +23,12 @@ namespace Mayo { -class WidgetModelTreeBuilder_Xde::Module : public QObject, public PropertyGroup { +class WidgetModelTreeBuilder_Xde::Module : public PropertyGroup { public: - Module(const ApplicationPtr& app) - : QObject(app.get()), - PropertyGroup(app->settings()) + static Module* get() { - this->setObjectName("WidgetModelTreeBuilder_Xde::Module"); - auto settings = app->settings(); - settings->addSetting(&this->instanceNameFormat, AppModule::get(app)->groupId_application); - settings->addResetFunction(AppModule::get(app)->groupId_application, [=]{ - this->instanceNameFormat.setValue(NameFormat::Product); - }); - this->instanceNameFormat.mutableEnumeration().changeTrContext(WidgetModelTreeBuilder_Xde::textIdContext()); - } - - static Module* get(const ApplicationPtr& app) { - return app->findChild("WidgetModelTreeBuilder_Xde::Module", Qt::FindDirectChildrenOnly); + static Module module; + return &module; } enum class NameFormat { Instance, Product, Both }; @@ -75,6 +64,19 @@ class WidgetModelTreeBuilder_Xde::Module : public QObject, public PropertyGroup } PropertyEnum instanceNameFormat{ this, textId("instanceNameFormat") }; + +private: + Module() + : PropertyGroup(AppModule::get()->settings()) + { + auto appModule = AppModule::get(); + auto settings = appModule->settings(); + settings->addSetting(&this->instanceNameFormat, appModule->properties()->groupId_application); + settings->addResetFunction(appModule->properties()->groupId_application, [=]{ + this->instanceNameFormat.setValue(NameFormat::Product); + }); + this->instanceNameFormat.mutableEnumeration().changeTrContext(WidgetModelTreeBuilder_Xde::textIdContext()); + } }; bool WidgetModelTreeBuilder_Xde::supportsDocumentTreeNode(const DocumentTreeNode& node) const @@ -88,7 +90,7 @@ void WidgetModelTreeBuilder_Xde::refreshTextTreeItem( const TDF_Label labelNode = node.label(); TDF_LabelSequence seqLabelRefresh; if (XCaf::isShapeReference(labelNode) - && m_module->instanceNameTemplate().contains("%product")) + && Module::get()->instanceNameTemplate().contains("%product")) { XCAFDoc_ShapeTool::GetUsers( XCaf::shapeReferred(labelNode), @@ -112,20 +114,12 @@ QTreeWidgetItem* WidgetModelTreeBuilder_Xde::createTreeItem(const DocumentTreeNo return this->buildXdeTree(nullptr, node); } -// BEWARE Not thread-safe, should be called from main(GUI) thread -void WidgetModelTreeBuilder_Xde::registerGuiApplication(GuiApplication* guiApp) -{ - m_module = Module::get(guiApp->application()); - if (!m_module) - m_module = new Module(guiApp->application()); -} - WidgetModelTree_UserActions WidgetModelTreeBuilder_Xde::createUserActions(QObject *parent) { WidgetModelTree_UserActions userActions; auto group = new QActionGroup(parent); group->setExclusive(true); - for (const Enumeration::Item& item : m_module->instanceNameFormat.enumeration().items()) { + for (const Enumeration::Item& item : Module::get()->instanceNameFormat.enumeration().items()) { const QString actionText = to_QString(fmt::format(textIdTr("Show {}"), item.name.tr())); auto action = new QAction(actionText, parent); action->setCheckable(true); @@ -216,7 +210,7 @@ QTreeWidgetItem* WidgetModelTreeBuilder_Xde::buildXdeTree( QByteArray WidgetModelTreeBuilder_Xde::instanceNameFormat() const { - return QtCoreUtils::QByteArray_frowRawData(m_module->instanceNameFormat.name()); + return QtCoreUtils::QByteArray_frowRawData(Module::get()->instanceNameFormat.name()); } void WidgetModelTreeBuilder_Xde::setInstanceNameFormat(const QByteArray& format) @@ -224,7 +218,7 @@ void WidgetModelTreeBuilder_Xde::setInstanceNameFormat(const QByteArray& format) if (format == this->instanceNameFormat()) return; - m_module->instanceNameFormat.setValueByName(format.constData()); + Module::get()->instanceNameFormat.setValueByName(format.constData()); for (QTreeWidgetItemIterator it(this->treeWidget()); *it; ++it) { if (WidgetModelTree::holdsDocumentTreeNode(*it)) this->refreshXdeAssemblyNodeItemText(*it); @@ -234,7 +228,6 @@ void WidgetModelTreeBuilder_Xde::setInstanceNameFormat(const QByteArray& format) std::unique_ptr WidgetModelTreeBuilder_Xde::clone() const { auto builder = std::make_unique(); - builder->m_module = this->m_module; builder->m_isMergeXdeReferredShapeOn = this->m_isMergeXdeReferredShapeOn; return builder; } @@ -259,7 +252,7 @@ QString WidgetModelTreeBuilder_Xde::referenceItemText( { const QString instanceName = to_QString(CafUtils::labelAttrStdName(instanceLabel)).trimmed(); const QString productName = to_QString(CafUtils::labelAttrStdName(productLabel)).trimmed(); - const QByteArray strTemplate = Module::toInstanceNameTemplate(m_module->instanceNameFormat); + const QByteArray strTemplate = Module::toInstanceNameTemplate(Module::get()->instanceNameFormat); QString itemText = QString::fromUtf8(strTemplate); itemText.replace("%instance", instanceName) .replace("%product", productName); diff --git a/src/app/widget_model_tree_builder_xde.h b/src/app/widget_model_tree_builder_xde.h index 7d794843..5c0898c2 100644 --- a/src/app/widget_model_tree_builder_xde.h +++ b/src/app/widget_model_tree_builder_xde.h @@ -18,7 +18,6 @@ class WidgetModelTreeBuilder_Xde : public WidgetModelTreeBuilder { void refreshTextTreeItem(const DocumentTreeNode& node, QTreeWidgetItem* treeItem) override; QTreeWidgetItem* createTreeItem(const DocumentTreeNode& node) override; - void registerGuiApplication(GuiApplication* guiApp) override; WidgetModelTree_UserActions createUserActions(QObject* parent) override; std::unique_ptr clone() const override; @@ -39,7 +38,6 @@ class WidgetModelTreeBuilder_Xde : public WidgetModelTreeBuilder { QByteArray instanceNameFormat() const; void setInstanceNameFormat(const QByteArray& format); - Module* m_module = nullptr; bool m_isMergeXdeReferredShapeOn = true; }; diff --git a/src/app/widget_occ_view.cpp b/src/app/widget_occ_view.cpp index 36e32552..a3a62fd6 100644 --- a/src/app/widget_occ_view.cpp +++ b/src/app/widget_occ_view.cpp @@ -14,29 +14,13 @@ #include "occt_window.h" #include - -#include -#include #if OCC_VERSION_HEX >= 0x070600 # include -# include -# include #endif namespace Mayo { -namespace { - -Handle_Aspect_DisplayConnection createDisplayConnection() -{ -#if (!defined(Q_OS_WIN) && (!defined(Q_OS_MAC) || defined(MACOSX_USE_GLX))) - return new Aspect_DisplayConnection(std::getenv("DISPLAY")); -#else - return new Aspect_DisplayConnection; -#endif -} - -IWidgetOccView::Creator& getWidgetOccViewCreator() +static IWidgetOccView::Creator& getWidgetOccViewCreator() { static IWidgetOccView::Creator fn = [](const Handle_V3d_View& view, QWidget* parent) { return new QWidgetOccView(view, parent); @@ -44,9 +28,6 @@ IWidgetOccView::Creator& getWidgetOccViewCreator() return fn; } -} // namespace - - void IWidgetOccView::setCreator(IWidgetOccView::Creator fn) { getWidgetOccViewCreator() = std::move(fn); @@ -58,10 +39,31 @@ IWidgetOccView* IWidgetOccView::create(const Handle_V3d_View& view, QWidget* par return fn(view, parent); } - #if OCC_VERSION_HEX >= 0x070600 -constexpr bool isCoreProfile = true; +// Defined in widget_occ_view.cpp +bool QOpenGLWidgetOccView_isCoreProfile(); +void QOpenGLWidgetOccView_createOpenGlContext(std::function fnCallback); +Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView_createCompatibleGraphicsDriver(); +bool QOpenGLWidgetOccView_wrapFrameBuffer(const Handle_Graphic3d_GraphicDriver&); +Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const Handle_Graphic3d_GraphicDriver&); + + +static Handle_Aspect_NeutralWindow createNativeWindow([[maybe_unused]] QWidget* widget) +{ + auto window = new Aspect_NeutralWindow; + // On non-Windows systems Aspect_Drawable is aliased to 'unsigned long' so can't init with nullptr + Aspect_Drawable nativeWin = 0; +#ifdef Q_OS_WIN + HDC wglDevCtx = wglGetCurrentDC(); + HWND wglWin = WindowFromDC(wglDevCtx); + nativeWin = (Aspect_Drawable)wglWin; +#else + nativeWin = (Aspect_Drawable)widget->winId(); +#endif + window->SetNativeHandle(nativeWin); + return window; +} QOpenGLWidgetOccView::QOpenGLWidgetOccView(const Handle_V3d_View& view, QWidget* parent) : QOpenGLWidget(parent), @@ -76,14 +78,20 @@ QOpenGLWidgetOccView::QOpenGLWidgetOccView(const Handle_V3d_View& view, QWidget* QSurfaceFormat glFormat; glFormat.setDepthBufferSize(24); glFormat.setStencilBufferSize(8); - if (isCoreProfile) + if (QOpenGLWidgetOccView_isCoreProfile()) glFormat.setVersion(4, 5); - glFormat.setProfile(isCoreProfile ? QSurfaceFormat::CoreProfile : QSurfaceFormat::CompatibilityProfile); -#if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) - glFormat.setColorSpace(QSurfaceFormat::sRGBColorSpace); - this->setTextureFormat(GL_SRGB8_ALPHA8); -#endif + glFormat.setProfile( + QOpenGLWidgetOccView_isCoreProfile() ? + QSurfaceFormat::CoreProfile : + QSurfaceFormat::CompatibilityProfile + ); + // Use QtOccFrameBuffer fallback + // To request sRGBColorSpace colorspace to meet OCCT expectations then consider code below: + // #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) + // glFormat.setColorSpace(QSurfaceFormat::sRGBColorSpace); + // this->setTextureFormat(GL_SRGB8_ALPHA8); + // #endif this->setFormat(glFormat); } @@ -99,78 +107,34 @@ QOpenGLWidgetOccView* QOpenGLWidgetOccView::create(const Handle_V3d_View& view, Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView::createCompatibleGraphicsDriver() { - auto gfxDriver = new OpenGl_GraphicDriver(createDisplayConnection(), false/*dontInit*/); - // Let QOpenGLWidget manage buffer swap - gfxDriver->ChangeOptions().buffersNoSwap = true; - // Don't write into alpha channel - gfxDriver->ChangeOptions().buffersOpaqueAlpha = true; - // Offscreen FBOs should be always used - gfxDriver->ChangeOptions().useSystemBuffer = false; -# if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - Message::SendWarning("Warning! Qt 5.10+ is required for sRGB setup.\n" - "Colors in 3D Viewer might look incorrect (Qt " QT_VERSION_STR " is used).\n"); - gfxDriver->ChangeOptions().sRGBDisable = true; -# endif - - return gfxDriver; + return QOpenGLWidgetOccView_createCompatibleGraphicsDriver(); } void QOpenGLWidgetOccView::initializeGL() { const QRect wrect = this->rect(); const Graphic3d_Vec2i viewSize(wrect.right() - wrect.left(), wrect.bottom() - wrect.top()); + QOpenGLWidgetOccView_createOpenGlContext([=](Aspect_RenderingContext context) { + auto window = Handle_Aspect_NeutralWindow::DownCast(this->v3dView()->Window()); + if (!window) + window = createNativeWindow(this); - Handle_OpenGl_Context glCtx = new OpenGl_Context(); - if (!glCtx->Init(isCoreProfile)) { - Message::SendFail() << "Error: OpenGl_Context is unable to wrap OpenGL context"; - return; - } - - auto window = Handle_Aspect_NeutralWindow::DownCast(this->v3dView()->Window()); - if (window) { window->SetSize(viewSize.x(), viewSize.y()); - this->v3dView()->SetWindow(window, glCtx->RenderingContext()); - } - else { - window = new Aspect_NeutralWindow; - Aspect_Drawable nativeWin = nullptr; -#ifdef Q_OS_WIN - HDC wglDevCtx = wglGetCurrentDC(); - HWND wglWin = WindowFromDC(wglDevCtx); - nativeWin = (Aspect_Drawable)wglWin; -#else - nativeWin = (Aspect_Drawable)this->winId(); -#endif - window->SetNativeHandle(nativeWin); - window->SetSize(viewSize.x(), viewSize.y()); - this->v3dView()->SetWindow(window, glCtx->RenderingContext()); - } - - //dumpGlInfo(true); + this->v3dView()->SetWindow(window, context); + }); } void QOpenGLWidgetOccView::paintGL() { - if (this->v3dView()->Window().IsNull()) + if (!this->v3dView()->Window()) return; - // Wrap FBO created by QOpenGLWidget - auto driver = Handle_OpenGl_GraphicDriver::DownCast(this->v3dView()->Viewer()->Driver()); - const Handle_OpenGl_Context& glCtx = driver->GetSharedContext(); - Handle_OpenGl_FrameBuffer defaultFbo = glCtx->DefaultFrameBuffer(); - if (!defaultFbo) { - defaultFbo = new OpenGl_FrameBuffer(); - glCtx->SetDefaultFrameBuffer(defaultFbo); - } - - if (!defaultFbo->InitWrapper(glCtx)) { - defaultFbo.Nullify(); - Message::SendFail() << "Default FBO wrapper creation failed"; - return; - } + const Handle(Graphic3d_GraphicDriver)& driver = this->v3dView()->Viewer()->Driver(); + if (!QOpenGLWidgetOccView_wrapFrameBuffer(driver)) + return; Graphic3d_Vec2i viewSizeOld; - const Graphic3d_Vec2i viewSizeNew = defaultFbo->GetVPSize(); + const Graphic3d_Vec2i viewSizeNew = QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(driver); auto window = Handle_Aspect_NeutralWindow::DownCast(this->v3dView()->Window()); window->Size(viewSizeOld.x(), viewSizeOld.y()); if (viewSizeNew != viewSizeOld) { @@ -200,9 +164,11 @@ QWidgetOccView::QWidgetOccView(const Handle_V3d_View& view, QWidget* parent) this->setAttribute(Qt::WA_PaintOnScreen); } +// Defined in widget_occ_view.cpp +Handle_Graphic3d_GraphicDriver QWidgetOccView_createCompatibleGraphicsDriver(); Handle_Graphic3d_GraphicDriver QWidgetOccView::createCompatibleGraphicsDriver() { - return new OpenGl_GraphicDriver(createDisplayConnection()); + return QWidgetOccView_createCompatibleGraphicsDriver(); } void QWidgetOccView::redraw() diff --git a/src/app/widget_occ_view.h b/src/app/widget_occ_view.h index 09f9bffe..a1d83554 100644 --- a/src/app/widget_occ_view.h +++ b/src/app/widget_occ_view.h @@ -12,7 +12,7 @@ #include #if OCC_VERSION_HEX >= 0x070600 -# include +# include // WARNING Qt5 / Qt6 #endif #include @@ -68,7 +68,7 @@ class QWidgetOccView : public QWidget, public IWidgetOccView { QWidgetOccView(const Handle_V3d_View& view, QWidget* parent = nullptr); void redraw() override; - virtual QWidget* widget() { return this; } + QWidget* widget() override { return this; } bool supportsWidgetOpacity() const override { return false; } static QWidgetOccView* create(const Handle_V3d_View& view, QWidget* parent); diff --git a/src/app/widget_occ_view_controller.cpp b/src/app/widget_occ_view_controller.cpp index 1c979aea..6bf612c2 100644 --- a/src/app/widget_occ_view_controller.cpp +++ b/src/app/widget_occ_view_controller.cpp @@ -9,6 +9,7 @@ #include "theme.h" #include +#include #include #include #include @@ -16,6 +17,7 @@ #include #include #include +#include namespace Mayo { @@ -25,32 +27,26 @@ static const QCursor& rotateCursor() { static QCursor cursor; if (!cursor.bitmap()) { - constexpr int rotateCursorWidth = 16; - constexpr int rotateCursorHeight = 16; - constexpr int rotateCursorByteCount = ((rotateCursorWidth + 7) / 8) * rotateCursorHeight; - constexpr int rotateCursorHotX = 6; - constexpr int rotateCursorHotY = 8; + constexpr int cursorWidth = 16; + constexpr int cursorHeight = 16; + constexpr int cursorHotX = 6; + constexpr int cursorHotY = 8; - static unsigned char rotateCursorBitmap[rotateCursorByteCount] = { + static unsigned char cursorBitmap[] = { 0xf0, 0xef, 0x18, 0xb8, 0x0c, 0x90, 0xe4, 0x83, 0x34, 0x86, 0x1c, 0x83, 0x00, 0x81, 0x00, 0xff, 0xff, 0x00, 0x81, 0x00, 0xc1, 0x38, 0x61, 0x2c, 0xc1, 0x27, 0x09, 0x30, 0x1d, 0x18, 0xf7, 0x0f }; - - static unsigned char rotateCursorMaskBitmap[rotateCursorByteCount] = { - 0xf0,0xef,0xf8,0xff,0xfc,0xff,0xfc,0xff,0x3c,0xfe,0x1c,0xff,0x00,0xff,0x00, - 0xff,0xff,0x00,0xff,0x00,0xff,0x38,0x7f,0x3c,0xff,0x3f,0xff,0x3f,0xff,0x1f, - 0xf7,0x0f + static unsigned char cursorMaskBitmap[] = { + 0xf0, 0xef, 0xf8, 0xff, 0xfc, 0xff, 0xfc, 0xff, 0x3c, 0xfe, 0x1c, 0xff, 0x00, 0xff, 0x00, + 0xff, 0xff, 0x00, 0xff, 0x00, 0xff, 0x38, 0x7f, 0x3c, 0xff, 0x3f, 0xff, 0x3f, 0xff, 0x1f, + 0xf7, 0x0f }; - const QBitmap cursorBmp = QBitmap::fromData( - QSize(rotateCursorWidth, rotateCursorHeight), - rotateCursorBitmap); - const QBitmap maskBmp = QBitmap::fromData( - QSize(rotateCursorWidth, rotateCursorHeight), - rotateCursorMaskBitmap); - const QCursor tempCursor(cursorBmp, maskBmp, rotateCursorHotX, rotateCursorHotY); + const QBitmap cursorBmp = QBitmap::fromData({ cursorWidth, cursorHeight }, cursorBitmap); + const QBitmap maskBmp = QBitmap::fromData({ cursorWidth, cursorHeight }, cursorMaskBitmap); + const QCursor tempCursor(cursorBmp, maskBmp, cursorHotX, cursorHotY); cursor = std::move(tempCursor); } @@ -104,9 +100,14 @@ class RubberBandWidget : public RubberBandWidget_ParentType { WidgetOccViewController::WidgetOccViewController(IWidgetOccView* occView) : V3dViewController(occView->v3dView(), occView->widget()), - m_occView(occView) + m_occView(occView), + m_navigStyle(NavigationStyle::Catia), + m_actionMatcher(createActionMatcher(m_navigStyle, &m_inputSequence)) { m_occView->widget()->installEventFilter(this); + m_inputSequence.setPrePushCallback([=](Input in) { m_actionMatcher->onInputPrePush(in); }); + m_inputSequence.setPreReleaseCallback([=](Input in) { m_actionMatcher->onInputPreRelease(in); }); + m_inputSequence.setClearCallback([=] { m_actionMatcher->onInputCleared(); }); } bool WidgetOccViewController::eventFilter(QObject* watched, QEvent* event) @@ -115,6 +116,7 @@ bool WidgetOccViewController::eventFilter(QObject* watched, QEvent* event) return false; if (event->type() == QEvent::Enter) { + m_inputSequence.clear(); m_occView->widget()->grabKeyboard(); return false; } @@ -123,7 +125,15 @@ bool WidgetOccViewController::eventFilter(QObject* watched, QEvent* event) return false; } - return this->handleEvent(event); + this->handleEvent(event); + return false; +} + +void WidgetOccViewController::setNavigationStyle(NavigationStyle style) +{ + m_navigStyle = style; + m_inputSequence.clear(); + m_actionMatcher = createActionMatcher(style, &m_inputSequence); } void WidgetOccViewController::redrawView() @@ -138,6 +148,8 @@ void WidgetOccViewController::startDynamicAction(V3dViewController::DynamicActio this->setViewCursor(Internal::rotateCursor()); else if (action == DynamicAction::Panning) this->setViewCursor(Qt::SizeAllCursor); + else if (action == DynamicAction::Zoom) + this->setViewCursor(Qt::SizeVerCursor); else if (action == DynamicAction::WindowZoom) this->setViewCursor(Qt::SizeBDiagCursor); @@ -174,89 +186,308 @@ struct WidgetOccViewController::RubberBand : public V3dViewController::AbstractR Internal::RubberBandWidget m_rubberBand; }; -V3dViewController::AbstractRubberBand* WidgetOccViewController::createRubberBand() +std::unique_ptr WidgetOccViewController::createRubberBand() { - return new RubberBand(m_occView->widget()); + return std::make_unique(m_occView->widget()); } -bool WidgetOccViewController::handleEvent(QEvent* event) +void WidgetOccViewController::handleEvent(const QEvent* event) { switch (event->type()) { - case QEvent::KeyPress: { - auto keyEvent = static_cast(event); - if (keyEvent->isAutoRepeat()) - return false; + case QEvent::KeyPress: + this->handleKeyPress(static_cast(event)); + break; + case QEvent::KeyRelease: + this->handleKeyRelease(static_cast(event)); + break; + case QEvent::MouseButtonPress: + this->handleMouseButtonPress(static_cast(event)); + break; + case QEvent::MouseMove: + this->handleMouseMove(static_cast(event)); + break; + case QEvent::MouseButtonRelease: + this->handleMouseButtonRelease(static_cast(event)); + break; + case QEvent::Wheel: + this->handleMouseWheel(static_cast(event)); + break; + default: + break; + } // end switch +} - if (keyEvent->key() == Qt::Key_Space && keyEvent->modifiers() == Qt::NoModifier) - this->startInstantZoom(m_occView->widget()->mapFromGlobal(QCursor::pos())); +void WidgetOccViewController::handleKeyPress(const QKeyEvent* event) +{ + if (event->isAutoRepeat()) + return; - if (keyEvent->key() == Qt::Key_Shift && !this->hasCurrentDynamicAction()) - emit this->multiSelectionToggled(true); + m_inputSequence.push(event->key()); + if (m_inputSequence.equal({ Qt::Key_Space })) + this->startInstantZoom(m_occView->widget()->mapFromGlobal(QCursor::pos())); - break; + if (m_inputSequence.equal({ Qt::Key_Shift }) && !this->hasCurrentDynamicAction()) + emit this->multiSelectionToggled(true); +} + +void WidgetOccViewController::handleKeyRelease(const QKeyEvent* event) +{ + if (event->isAutoRepeat()) + return; + + m_inputSequence.release(event->key()); + if (!m_inputSequence.equal({})) + return; + + if (m_inputSequence.lastInput() == Qt::Key_Space && this->currentDynamicAction() == DynamicAction::InstantZoom) + this->stopInstantZoom(); + + if (m_inputSequence.lastInput() == Qt::Key_Shift && !this->hasCurrentDynamicAction()) + emit this->multiSelectionToggled(false); +} + +void WidgetOccViewController::handleMouseButtonPress(const QMouseEvent* event) +{ + m_inputSequence.push(event->button()); + const QPoint currPos = m_occView->widget()->mapFromGlobal(event->globalPos()); + m_prevPos = currPos; +} + +void WidgetOccViewController::handleMouseMove(const QMouseEvent* event) +{ + const QPoint currPos = m_occView->widget()->mapFromGlobal(event->globalPos()); + const QPoint prevPos = m_prevPos; + m_prevPos = currPos; + if (m_actionMatcher->matchRotation()) + this->rotation(currPos); + else if (m_actionMatcher->matchPan()) + this->pan(prevPos, currPos); + else if (m_actionMatcher->matchZoom()) + this->zoom(prevPos, currPos); + else if (m_actionMatcher->matchWindowZoom()) + this->windowZoomRubberBand(currPos); + else + emit mouseMoved(currPos); +} + +void WidgetOccViewController::handleMouseButtonRelease(const QMouseEvent* event) +{ + m_inputSequence.release(event->button()); + const bool hadDynamicAction = this->hasCurrentDynamicAction(); + if (this->isWindowZoomingStarted()) + this->windowZoom(m_occView->widget()->mapFromGlobal(event->globalPos())); + + this->stopDynamicAction(); + if (!hadDynamicAction) + emit mouseClicked(event->button()); +} + +void WidgetOccViewController::handleMouseWheel(const QWheelEvent* event) +{ + const QPoint delta = event->angleDelta(); + if (delta.y() > 0 || (delta.y() == 0 && delta.x() > 0)) + this->zoomIn(); + else + this->zoomOut(); +} + +class WidgetOccViewController::Mayo_ActionMatcher : public ActionMatcher { +public: + Mayo_ActionMatcher(const InputSequence* seq) : ActionMatcher(seq) {} + + bool matchRotation() const override { + return this->inputs.equal({ Qt::LeftButton }); } - case QEvent::KeyRelease: { - auto keyEvent = static_cast(event); - if (keyEvent->isAutoRepeat()) - return false; - if (keyEvent->key() == Qt::Key_Space && this->currentDynamicAction() == DynamicAction::InstantZoom) - this->stopInstantZoom(); + bool matchPan() const override { + return this->inputs.equal({ Qt::RightButton }); + } - if (keyEvent->key() == Qt::Key_Shift && !this->hasCurrentDynamicAction()) - emit this->multiSelectionToggled(false); + bool matchZoom() const override { + return this->inputs.equal({ Qt::LeftButton, Qt::RightButton }) + || this->inputs.equal({ Qt::RightButton, Qt::LeftButton }); + } - break; + bool matchWindowZoom() const override { + return this->inputs.equal({ Qt::Key_Control, Qt::LeftButton }); } - case QEvent::MouseButtonPress: { - auto mouseEvent = static_cast(event); - const QPoint currPos = m_occView->widget()->mapFromGlobal(mouseEvent->globalPos()); - m_prevPos = currPos; - break; +}; + +class WidgetOccViewController::Catia_ActionMatcher : public ActionMatcher { +public: + Catia_ActionMatcher(const InputSequence* seq) : ActionMatcher(seq) { + m_timer.start(); } - case QEvent::MouseMove: { - auto mouseEvent = static_cast(event); - const QPoint currPos = m_occView->widget()->mapFromGlobal(mouseEvent->globalPos()); - const QPoint prevPos = m_prevPos; - m_prevPos = currPos; - if (mouseEvent->buttons() == Qt::LeftButton) - this->rotation(currPos); - else if (mouseEvent->buttons() == Qt::RightButton) - this->pan(prevPos, currPos); - else if (mouseEvent->buttons() == Qt::MiddleButton) - this->windowZoomRubberBand(currPos); - else - emit mouseMoved(currPos); - break; + bool matchRotation() const override { + return this->inputs.equal({ Qt::MiddleButton, Qt::LeftButton }) + || this->inputs.equal({ Qt::MiddleButton, Qt::RightButton }); } - case QEvent::MouseButtonRelease: { - auto mouseEvent = static_cast(event); - const bool hadDynamicAction = this->hasCurrentDynamicAction(); - if (this->isWindowZoomingStarted()) - this->windowZoom(m_occView->widget()->mapFromGlobal(mouseEvent->globalPos())); - this->stopDynamicAction(); - if (!hadDynamicAction) - emit mouseClicked(mouseEvent->button()); + bool matchPan() const override { + return this->inputs.equal({ Qt::MiddleButton }) && !this->matchZoom(); + } - break; + bool matchZoom() const override { + return this->inputs.equal({ Qt::MiddleButton }) + && m_beforeLastOp == InputSequence::Operation::Push + && (m_beforeLastInput == Qt::LeftButton || m_beforeLastInput == Qt::RightButton) + && this->inputs.lastOperation() == InputSequence::Operation::Release + && this->inputs.lastInput() == m_beforeLastInput + && (m_lastTimestamp_ms - m_beforeLastTimestamp_ms) < 750 + ; } - case QEvent::Wheel: { - auto wheelEvent = static_cast(event); - const QPoint delta = wheelEvent->angleDelta(); - if (delta.y() > 0 || (delta.y() == 0 && delta.x() > 0)) - this->zoomIn(); - else - this->zoomOut(); - break; + bool matchWindowZoom() const override { + return this->inputs.equal({ Qt::Key_Control, Qt::MiddleButton }); } - default: - break; - } // end switch - return false; + void onInputPrePush(Input /*in*/) override { + this->recordBeforeLastOperation(); + } + + void onInputPreRelease(Input /*in*/) override { + this->recordBeforeLastOperation(); + } + + void onInputCleared() override { + m_beforeLastOp = InputSequence::Operation::None; + m_beforeLastInput = -1; + m_beforeLastTimestamp_ms = 0; + m_lastTimestamp_ms = 0; + m_timer.restart(); + } + +private: + void recordBeforeLastOperation() { + m_beforeLastOp = this->inputs.lastOperation(); + m_beforeLastInput = this->inputs.lastInput(); + m_beforeLastTimestamp_ms = m_lastTimestamp_ms; + m_lastTimestamp_ms = m_timer.elapsed(); + } + + InputSequence::Operation m_beforeLastOp = InputSequence::Operation::None; + Input m_beforeLastInput = -1; + int64_t m_beforeLastTimestamp_ms = 0; + int64_t m_lastTimestamp_ms = 0; + QElapsedTimer m_timer; +}; + +class WidgetOccViewController::SolidWorks_ActionMatcher : public ActionMatcher { +public: + SolidWorks_ActionMatcher(const InputSequence* seq) : ActionMatcher(seq) {} + + bool matchRotation() const override { + return this->inputs.equal({ Qt::MiddleButton }); + } + + bool matchPan() const override { + return this->inputs.equal({ Qt::Key_Control, Qt::MiddleButton }); + } + + bool matchZoom() const override { + return this->inputs.equal({ Qt::Key_Shift, Qt::MiddleButton });; + } + + bool matchWindowZoom() const override { + return this->inputs.equal({ Qt::Key_Control, Qt::LeftButton }); + } +}; + +class WidgetOccViewController::Unigraphics_ActionMatcher : public ActionMatcher { +public: + Unigraphics_ActionMatcher(const InputSequence* seq) : ActionMatcher(seq) {} + + bool matchRotation() const override { + return this->inputs.equal({ Qt::MiddleButton }); + } + + bool matchPan() const override { + return this->inputs.equal({ Qt::MiddleButton, Qt::RightButton }); + } + + bool matchZoom() const override { + return this->inputs.equal({ Qt::MiddleButton, Qt::LeftButton });; + } + + bool matchWindowZoom() const override { + return this->inputs.equal({ Qt::Key_Control, Qt::LeftButton }); + } +}; + +class WidgetOccViewController::ProEngineer_ActionMatcher : public ActionMatcher { +public: + ProEngineer_ActionMatcher(const InputSequence* seq) : ActionMatcher(seq) {} + + bool matchRotation() const override { + return this->inputs.equal({ Qt::MiddleButton }); + } + + bool matchPan() const override { + return this->inputs.equal({ Qt::Key_Shift, Qt::MiddleButton }); + } + + bool matchZoom() const override { + return this->inputs.equal({ Qt::Key_Control, Qt::MiddleButton });; + } + + bool matchWindowZoom() const override { + return this->inputs.equal({ Qt::Key_Control, Qt::LeftButton }); + } +}; + +std::unique_ptr +WidgetOccViewController::createActionMatcher(NavigationStyle style, const InputSequence* seq) +{ + switch(style) { + case NavigationStyle::Mayo: return std::make_unique(seq); + case NavigationStyle::Catia: return std::make_unique(seq); + case NavigationStyle::SolidWorks: return std::make_unique(seq); + case NavigationStyle::Unigraphics: return std::make_unique(seq); + case NavigationStyle::ProEngineer: return std::make_unique(seq); + } + return {}; +} + +void WidgetOccViewController::InputSequence::push(Input in) +{ + auto itFound = std::find(m_inputs.cbegin(), m_inputs.cend(), in); + if (itFound != m_inputs.cend()) + m_inputs.erase(itFound); + + if (m_fnPrePushCallback) + m_fnPrePushCallback(in); + + m_inputs.push_back(in); + m_lastOperation = Operation::Push; + m_lastInput = in; +} + +void WidgetOccViewController::InputSequence::release(Input in) +{ + auto itRemoved = std::remove(m_inputs.begin(), m_inputs.end(), in); + if (itRemoved != m_inputs.end()) { + if (m_fnPreReleaseCallback) + m_fnPreReleaseCallback(in); + + m_inputs.erase(itRemoved, m_inputs.end()); + m_lastOperation = Operation::Release; + m_lastInput = in; + } +} + +void WidgetOccViewController::InputSequence::clear() +{ + m_inputs.clear(); + m_lastOperation = Operation::None; + m_lastInput = -1; + if (m_fnClearCallback) + m_fnClearCallback(); +} + +bool WidgetOccViewController::InputSequence::equal(std::initializer_list other) const +{ + return std::equal(m_inputs.cbegin(), m_inputs.cend(), other.begin(), other.end()); } } // namespace Mayo diff --git a/src/app/widget_occ_view_controller.h b/src/app/widget_occ_view_controller.h index 5d8f1704..c475b079 100644 --- a/src/app/widget_occ_view_controller.h +++ b/src/app/widget_occ_view_controller.h @@ -7,9 +7,16 @@ #pragma once #include "../graphics/v3d_view_controller.h" +#include "../base/span.h" +#include +#include +#include class QCursor; +class QKeyEvent; +class QMouseEvent; class QRubberBand; +class QWheelEvent; namespace Mayo { @@ -22,6 +29,11 @@ class WidgetOccViewController : public V3dViewController { bool eventFilter(QObject* watched, QEvent* event) override; + enum class NavigationStyle { + Mayo, Catia, SolidWorks, Unigraphics, ProEngineer + }; + void setNavigationStyle(NavigationStyle style); + signals: void multiSelectionToggled(bool on); @@ -34,13 +46,82 @@ class WidgetOccViewController : public V3dViewController { void setViewCursor(const QCursor& cursor); - AbstractRubberBand* createRubberBand() override; + std::unique_ptr createRubberBand() override; struct RubberBand; - bool handleEvent(QEvent* event); + void handleEvent(const QEvent* event); + void handleKeyPress(const QKeyEvent* event); + void handleKeyRelease(const QKeyEvent* event); + void handleMouseButtonPress(const QMouseEvent* event); + void handleMouseMove(const QMouseEvent* event); + void handleMouseButtonRelease(const QMouseEvent* event); + void handleMouseWheel(const QWheelEvent* event); + + // -- Action matching + + // User input: key, mouse button, ... + using Input = int; + + // Sequence of user inputs being "on" : key pressed, mouse button pressed, ... + class InputSequence { + public: + void push(Input in); + void release(Input in); + void clear(); + Span data() const { return m_inputs; } + + enum class Operation { None, Push, Release }; + Operation lastOperation() const { return m_lastOperation; } + Input lastInput() const { return m_lastInput; } + + bool equal(std::initializer_list other) const; + + void setPrePushCallback(std::function fn) { m_fnPrePushCallback = std::move(fn); } + void setPreReleaseCallback(std::function fn) { m_fnPreReleaseCallback = std::move(fn); } + void setClearCallback(std::function fn) { m_fnClearCallback = std::move(fn); } + + private: + std::vector m_inputs; + Operation m_lastOperation = Operation::None; + Input m_lastInput = -1; + std::function m_fnPrePushCallback; + std::function m_fnPreReleaseCallback; + std::function m_fnClearCallback; + }; + + // Base class to provide matching of DynamicAction from an InputSequence object + class ActionMatcher { + public: + ActionMatcher(const InputSequence* seq) : inputs(*seq) {} + virtual ~ActionMatcher() = default; + + virtual bool matchRotation() const = 0; + virtual bool matchPan() const = 0; + virtual bool matchZoom() const = 0; + virtual bool matchWindowZoom() const = 0; + + virtual void onInputPrePush(Input) {} + virtual void onInputPreRelease(Input) {} + virtual void onInputCleared() {} + + const InputSequence& inputs; + }; + + // Fabrication to create corresponding ActionMatcher from navigation style + static std::unique_ptr createActionMatcher(NavigationStyle style, const InputSequence* seq); + class Mayo_ActionMatcher; + class Catia_ActionMatcher; + class SolidWorks_ActionMatcher; + class Unigraphics_ActionMatcher; + class ProEngineer_ActionMatcher; + + // -- Attributes IWidgetOccView* m_occView = nullptr; QPoint m_prevPos; + NavigationStyle m_navigStyle = NavigationStyle::Mayo; + InputSequence m_inputSequence; + std::unique_ptr m_actionMatcher; }; } // namespace Mayo diff --git a/src/app/widget_occ_view_impl.cpp b/src/app/widget_occ_view_impl.cpp new file mode 100644 index 00000000..d4deaa9a --- /dev/null +++ b/src/app/widget_occ_view_impl.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include + +#include +#include +#include +#if OCC_VERSION_HEX >= 0x070600 +# include +# include +# include +#endif + +#include + +namespace Mayo { + +namespace { + +Handle_Aspect_DisplayConnection createDisplayConnection() +{ +#if (!defined(Q_OS_WIN) && (!defined(Q_OS_MAC) || defined(MACOSX_USE_GLX))) + return new Aspect_DisplayConnection(std::getenv("DISPLAY")); +#else + return new Aspect_DisplayConnection; +#endif +} + +} // namespace + +#if OCC_VERSION_HEX >= 0x070600 +namespace { + +// OpenGL FBO subclass for wrapping FBO created by Qt using GL_RGBA8 texture format instead of GL_SRGB8_ALPHA8. +// This FBO is set to OpenGl_Context::SetDefaultFrameBuffer() as a final target. +// Subclass calls OpenGl_Context::SetFrameBufferSRGB() with sRGB=false flag, +// which asks OCCT to disable GL_FRAMEBUFFER_SRGB and apply sRGB gamma correction manually. +// +// Note this is using patch https://github.com/gkv311/occt-samples-qopenglwidget/commit/32c997ce281422ce7dcf4f7e1e529fbdf7dc642c +// See also https://github.com/gkv311/occt-samples-qopenglwidget/issues/3 +class QtOccFrameBuffer : public OpenGl_FrameBuffer { + DEFINE_STANDARD_RTTI_INLINE(QtOccFrameBuffer, OpenGl_FrameBuffer) +public: + QtOccFrameBuffer() {} + + void BindBuffer(const Handle(OpenGl_Context)& ctx) override + { + OpenGl_FrameBuffer::BindBuffer(ctx); + ctx->SetFrameBufferSRGB(true, false); + } + + void BindDrawBuffer(const Handle(OpenGl_Context)& ctx) override + { + OpenGl_FrameBuffer::BindDrawBuffer(ctx); + ctx->SetFrameBufferSRGB(true, false); + } + + void BindReadBuffer(const Handle(OpenGl_Context)& ctx) override + { + OpenGl_FrameBuffer::BindReadBuffer(ctx); + } +}; + +} // namespace + +bool QOpenGLWidgetOccView_isCoreProfile() +{ + return false; +} + +void QOpenGLWidgetOccView_createOpenGlContext(std::function fnCallback) +{ + Handle_OpenGl_Context glCtx = new OpenGl_Context; + if (!glCtx->Init(QOpenGLWidgetOccView_isCoreProfile())) { + Message::SendFail() << "Error: OpenGl_Context is unable to wrap OpenGL context"; + return; + } + + fnCallback(glCtx->RenderingContext()); +} + +Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView_createCompatibleGraphicsDriver() +{ + auto gfxDriver = new OpenGl_GraphicDriver(createDisplayConnection(), false/*dontInit*/); + // Let QOpenGLWidget manage buffer swap + gfxDriver->ChangeOptions().buffersNoSwap = true; + // Don't write into alpha channel + gfxDriver->ChangeOptions().buffersOpaqueAlpha = true; + // Offscreen FBOs should be always used + gfxDriver->ChangeOptions().useSystemBuffer = false; +#if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) + Message::SendWarning("Warning! Qt 5.10+ is required for sRGB setup.\n" + "Colors in 3D Viewer might look incorrect (Qt " QT_VERSION_STR " is used).\n"); + gfxDriver->ChangeOptions().sRGBDisable = true; +#endif + + return gfxDriver; +} + +bool QOpenGLWidgetOccView_wrapFrameBuffer(const Handle_Graphic3d_GraphicDriver& gfxDriver) +{ + // Wrap FBO created by QOpenGLWidget + auto driver = Handle_OpenGl_GraphicDriver::DownCast(gfxDriver); + if (!driver) + return false; + + const Handle_OpenGl_Context& glCtx = driver->GetSharedContext(); + Handle_OpenGl_FrameBuffer defaultFbo = glCtx->DefaultFrameBuffer(); + if (!defaultFbo) { + //defaultFbo = new OpenGl_FrameBuffer; + defaultFbo = new QtOccFrameBuffer; + glCtx->SetDefaultFrameBuffer(defaultFbo); + } + + if (!defaultFbo->InitWrapper(glCtx)) { + defaultFbo.Nullify(); + Message::SendFail() << "Default FBO wrapper creation failed"; + return false; + } + + return true; +} + +Graphic3d_Vec2i QOpenGLWidgetOccView_getDefaultframeBufferViewportSize(const Handle_Graphic3d_GraphicDriver& gfxDriver) +{ + auto driver = Handle_OpenGl_GraphicDriver::DownCast(gfxDriver); + return driver->GetSharedContext()->DefaultFrameBuffer()->GetVPSize(); +} + +#endif // OCC_VERSION_HEX >= 0x070600 + +Handle_Graphic3d_GraphicDriver QWidgetOccView_createCompatibleGraphicsDriver() +{ + return new OpenGl_GraphicDriver(createDisplayConnection()); +} + +} // namespace Mayo diff --git a/src/app/widget_shape_selector.cpp b/src/app/widget_shape_selector.cpp deleted file mode 100644 index 215dd40f..00000000 --- a/src/app/widget_shape_selector.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "widget_shape_selector.h" - -#if 0 -#include "../gui/gui_document.h" -#include "widget_gui_document.h" -#include "widget_occ_view_controller.h" - -#include -#include -#include -#include -#include -#include -#include - -#include - -namespace Mayo { - -static QString stringShapeTypePlural(TopAbs_ShapeEnum shapeType) -{ - switch (shapeType) { - case TopAbs_VERTEX: return WidgetShapeSelector::tr("Vertices"); - case TopAbs_EDGE: return WidgetShapeSelector::tr("Edges"); - case TopAbs_WIRE: return WidgetShapeSelector::tr("Wires"); - case TopAbs_FACE: return WidgetShapeSelector::tr("Faces"); - case TopAbs_SHELL: return WidgetShapeSelector::tr("Shells"); - case TopAbs_SOLID: return WidgetShapeSelector::tr("Solids"); - case TopAbs_COMPOUND: return WidgetShapeSelector::tr("Compounds"); - case TopAbs_COMPSOLID: return WidgetShapeSelector::tr("Connected solids"); - case TopAbs_SHAPE: return WidgetShapeSelector::tr("?Shapes?"); - } - return QString("?"); -} - -static const TopAbs_ShapeEnum allShapeTypes[] = { - TopAbs_VERTEX, TopAbs_EDGE, TopAbs_WIRE, TopAbs_FACE, TopAbs_SHELL, TopAbs_SOLID -}; - -WidgetShapeSelector::WidgetShapeSelector(WidgetGuiDocument* widgetGuiDoc) - : WidgetShapeSelector(allShapeTypes, widgetGuiDoc) -{ -} - -WidgetShapeSelector::WidgetShapeSelector( - Span spanShapeType, - WidgetGuiDocument* widgetGuiDoc) - : QWidget(widgetGuiDoc), - m_widgetGuiDoc(widgetGuiDoc), - m_selector(new GpxShapeSelector(widgetGuiDoc->guiDocument())) -{ - Q_ASSERT(widgetGuiDoc); - - this->setAutoFillBackground(true); - auto comboBox = new QComboBox(this); - m_comboBoxShapeType = comboBox; - for (TopAbs_ShapeEnum shapeType : spanShapeType) - comboBox->addItem(tr("Select %1").arg(stringShapeTypePlural(shapeType)), shapeType); - m_btnBox = new QDialogButtonBox(this); - m_btnBox->setCenterButtons(true); - - auto mainLayout = new QVBoxLayout(this); - mainLayout->setContentsMargins(0, 0, 0, 0); - mainLayout->setSpacing(0); - mainLayout->addWidget(comboBox); - mainLayout->addWidget(m_btnBox); - - QObject::connect( - comboBox, QOverload::of(&QComboBox::activated), - this, &WidgetShapeSelector::onShapeSelectionModeChanged); - QObject::connect( - m_btnBox, &QDialogButtonBox::accepted, - this, &WidgetShapeSelector::accepted); - QObject::connect( - m_btnBox, &QDialogButtonBox::rejected, - this, &WidgetShapeSelector::rejected); - QObject::connect( - m_btnBox, &QDialogButtonBox::clicked, - this, &WidgetShapeSelector::buttonClicked); - m_selector->setViewController(widgetGuiDoc->controller()); - - auto escShortcut = new QShortcut(Qt::Key_Escape, this); - QObject::connect(escShortcut, &QShortcut::activated, this, &WidgetShapeSelector::rejected); - - this->onShapeSelectionModeChanged(comboBox->currentIndex()); -} - -WidgetShapeSelector::~WidgetShapeSelector() -{ - delete m_selector; -} - -void WidgetShapeSelector::addButton(QDialogButtonBox::StandardButton stdBtn) -{ - m_btnBox->addButton(stdBtn); -} - -void WidgetShapeSelector::addButton(QAbstractButton *btn, QDialogButtonBox::ButtonRole role) -{ - m_btnBox->addButton(btn, role); -} - -QDialogButtonBox::ButtonRole WidgetShapeSelector::buttonRole(QAbstractButton *btn) const -{ - return m_btnBox->buttonRole(btn); -} - -GpxShapeSelector* WidgetShapeSelector::selector() const -{ - return m_selector; -} - -void WidgetShapeSelector::onShapeSelectionModeChanged(int comboBoxItemId) -{ - bool okToInt = false; - const int iShapeType = - m_comboBoxShapeType->itemData(comboBoxItemId).toInt(&okToInt); - const TopAbs_ShapeEnum shapeType = - okToInt ? static_cast(iShapeType) : TopAbs_SHAPE; - m_selector->setShapeType(shapeType); -} - - -// -- -// -- ShapeSelector -// -- - -GpxShapeSelector::GpxShapeSelector(GuiDocument* guiDoc) - : QObject(guiDoc), - m_guiDocument(guiDoc), - m_shapeType(TopAbs_SHAPE) -{ - this->context()->RemoveFilters(); -} - -GpxShapeSelector::~GpxShapeSelector() -{ - this->clearSelection(); - this->context()->RemoveFilters(); -} - -V3dViewController* GpxShapeSelector::viewController() const -{ - return m_viewCtrl; -} - -void GpxShapeSelector::setViewController(V3dViewController *ctrl) -{ - if (ctrl == m_viewCtrl) - return; - - m_viewCtrl = ctrl; - QObject::connect( - ctrl, &V3dViewController::mouseMoved, - this, &GpxShapeSelector::onView3dMouseMove); - QObject::connect( - ctrl, &V3dViewController::mouseClicked, - this, &GpxShapeSelector::onView3dMouseClicked); -} - -TopAbs_ShapeEnum GpxShapeSelector::shapeType() const -{ - return m_shapeType; -} - -void GpxShapeSelector::setShapeType(TopAbs_ShapeEnum shapeEnum) -{ - m_shapeType = shapeEnum; - this->onShapeTypeChanged(shapeEnum); - emit shapeTypeChanged(shapeEnum); -} - -GpxShapeSelector::Mode GpxShapeSelector::mode() const -{ - return m_mode; -} - -void GpxShapeSelector::setMode(GpxShapeSelector::Mode mode) -{ - m_mode = mode; - this->clearSelection(); -} - -const GraphicsScene* GpxShapeSelector::graphicsScene() const -{ - return m_guiDocument->graphicsScene(); -} - -bool GpxShapeSelector::hasSelectedShapes() const -{ - auto gfxScene = m_guiDocument->graphicsScene(); - auto foundOwner = gfxScene->findSelectedGraphicsOwner([](const GraphicsObjectPtr& ptr) { - auto brepOwner = Handle_StdSelect_BRepOwner::DownCast(ptr); - return !brepOwner.IsNull() && brepOwner->HasShape(); - }); - return !foundOwner.IsNull(); -} - -void GpxShapeSelector::clearSelection() -{ - this->graphicsScene()->clearSelection(); - emit shapeSelectionCleared(); -} - -void GpxShapeSelector::onShapeTypeChanged(TopAbs_ShapeEnum shapeEnum) -{ - this->clearSelection(); - this->context()->RemoveFilters(); - if (shapeEnum != TopAbs_SHAPE) - this->context()->AddFilter(new StdSelect_ShapeTypeFilter(shapeEnum)); -} - -void GpxShapeSelector::onView3dMouseMove(const QPoint& pos) -{ - this->graphicsScene()->highlightAt(pos, m_guiDocument->v3dView()); -} - -void GpxShapeSelector::onView3dMouseClicked(Qt::MouseButton btn) -{ - const bool hadSelected = this->hasSelectedShapes(); - auto detectedEntity = Handle_StdSelect_BRepOwner::DownCast(this->context()->DetectedOwner()); - AIS_StatusOfPick pickStatus = AIS_SOP_NothingSelected; - if (!this->context()->HasDetected()) { - this->context()->ClearSelected(true); - } - else { - if (m_mode == Mode::Single) - pickStatus = this->context()->Select(true); - else if (m_mode == Mode::Multi) - pickStatus = this->context()->ShiftSelect(true); - } - - if (m_mode == Mode::Multi && hadSelected && !this->hasSelectedShapes()) - emit shapeSelectionCleared(); - - if (pickStatus == AIS_SOP_Error - || pickStatus == AIS_SOP_NothingSelected - || pickStatus == AIS_SOP_Removed) - { - return; - } - - if (btn == Qt::LeftButton - && !detectedEntity.IsNull() - && detectedEntity->HasShape()) - { - emit shapeClicked(detectedEntity->Shape()); - } -} - -} // namespace Mayo -#endif diff --git a/src/app/widget_shape_selector.h b/src/app/widget_shape_selector.h deleted file mode 100644 index 5b646b8e..00000000 --- a/src/app/widget_shape_selector.h +++ /dev/null @@ -1,98 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#pragma once - -#if 0 -#include "../base/span.h" -#include "../graphics/graphics_scene.h" -#include -#include -#include -class QComboBox; -class TopoDS_Shape; - -namespace Mayo { - -class V3dViewController; -class GuiDocument; - -class GpxShapeSelector : public QObject { - Q_OBJECT -public: - GpxShapeSelector(GuiDocument* guiDoc); - ~GpxShapeSelector(); - - V3dViewController* viewController() const; - void setViewController(V3dViewController* ctrl); - - TopAbs_ShapeEnum shapeType() const; - void setShapeType(TopAbs_ShapeEnum shapeEnum); - - enum class Mode { - None, - Single, - Multi - }; - - Mode mode() const; - void setMode(Mode mode); - - void clearSelection(); - -signals: - void shapeTypeChanged(TopAbs_ShapeEnum shapeType); - void shapeClicked(const TopoDS_Shape& shape); - void shapeSelectionCleared(); - -protected: - virtual void onShapeTypeChanged(TopAbs_ShapeEnum shapeEnum); - virtual void onView3dMouseMove(const QPoint& pos); - virtual void onView3dMouseClicked(Qt::MouseButton btn); - - const GraphicsScene* graphicsScene() const; - - bool hasSelectedShapes() const; - - GuiDocument* m_guiDocument = nullptr; - V3dViewController* m_viewCtrl = nullptr; - TopAbs_ShapeEnum m_shapeType; - Mode m_mode = Mode::Multi; -}; - -class WidgetGuiDocument; - -class WidgetShapeSelector : public QWidget { - Q_OBJECT -public: - WidgetShapeSelector(WidgetGuiDocument* widgetGuiDoc); - WidgetShapeSelector( - Span spanShapeType, - WidgetGuiDocument* widgetGuiDoc); - ~WidgetShapeSelector(); - - void addButton(QDialogButtonBox::StandardButton stdBtn); - void addButton(QAbstractButton* btn, QDialogButtonBox::ButtonRole role); - QDialogButtonBox::ButtonRole buttonRole(QAbstractButton* btn) const; - - GpxShapeSelector* selector() const; - -signals: - void buttonClicked(QAbstractButton* btn); - void accepted(); - void rejected(); - -private: - void onShapeSelectionModeChanged(int comboBoxItemId); - - Mayo::WidgetGuiDocument* m_widgetGuiDoc; - QComboBox* m_comboBoxShapeType; - QDialogButtonBox* m_btnBox; - GpxShapeSelector* m_selector; -}; - -} // namespace Mayo -#endif diff --git a/src/app/widgets_utils.h b/src/app/widgets_utils.h index 7926df6f..15ad9681 100644 --- a/src/app/widgets_utils.h +++ b/src/app/widgets_utils.h @@ -49,4 +49,12 @@ class WidgetsUtils { static void moveWidgetLeftTo(QWidget* widget, const QWidget* nextTo, int margin = 0); }; +#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) +// Qt6 QWidget::enterEvent(QEnterEvent*) +using QWidgetEnterEvent = QEnterEvent; +#else +// Qt5 QWidget::enterEvent(QEvent*) +using QWidgetEnterEvent = QEvent; +#endif + } // namespace Mayo diff --git a/src/base/application.cpp b/src/base/application.cpp index 0200a6ab..d2fd1ee8 100644 --- a/src/base/application.cpp +++ b/src/base/application.cpp @@ -5,11 +5,8 @@ ****************************************************************************/ #include "application.h" -#include "document_tree_node_properties_provider.h" #include "filepath_conv.h" -#include "io_system.h" #include "property_builtins.h" -#include "settings.h" #include "task_common.h" #include "tkernel_utils.h" @@ -30,24 +27,31 @@ namespace Mayo { class Document::FormatBinaryRetrievalDriver : public BinXCAFDrivers_DocumentRetrievalDriver { public: + FormatBinaryRetrievalDriver(const ApplicationPtr& app) : m_app(app) {} + #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 6, 0) - Handle(CDM_Document) CreateDocument() override { return new Document; } + Handle(CDM_Document) CreateDocument() override { return new Document(m_app); } #endif + +private: + ApplicationPtr m_app; }; class Document::FormatXmlRetrievalDriver : public XmlXCAFDrivers_DocumentRetrievalDriver { public: + FormatXmlRetrievalDriver(const ApplicationPtr& app) : m_app(app) {} + #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 6, 0) - Handle(CDM_Document) CreateDocument() override { return new Document; } + Handle(CDM_Document) CreateDocument() override { return new Document(m_app); } #endif + +private: + ApplicationPtr m_app; }; struct Application::Private { std::atomic m_seqDocumentIdentifier = {}; std::unordered_map m_mapIdentifierDocument; - Settings m_settings; - IO::System m_ioSystem; - DocumentTreeNodePropertiesProviderTable m_documentTreeNodePropertiesProviderTable; std::vector m_vecTranslator; }; @@ -64,11 +68,11 @@ const ApplicationPtr& Application::instance() const char strFougueCopyright[] = "Copyright (c) 2021, Fougue Ltd. "; appPtr->DefineFormat( Document::NameFormatBinary, qUtf8Printable(tr("Binary Mayo Document Format")), "myb", - new Document::FormatBinaryRetrievalDriver, + new Document::FormatBinaryRetrievalDriver(appPtr), new BinXCAFDrivers_DocumentStorageDriver); appPtr->DefineFormat( Document::NameFormatXml, qUtf8Printable(tr("XML Mayo Document Format")), "myx", - new Document::FormatXmlRetrievalDriver, + new Document::FormatXmlRetrievalDriver(appPtr), new XmlXCAFDrivers_DocumentStorageDriver(strFougueCopyright)); qRegisterMetaType("Mayo::TreeNodeId"); @@ -151,21 +155,6 @@ void Application::closeDocument(const DocumentPtr& doc) TDocStd_Application::Close(doc); } -Settings* Application::settings() const -{ - return &(d->m_settings); -} - -IO::System* Application::ioSystem() const -{ - return &(d->m_ioSystem); -} - -DocumentTreeNodePropertiesProviderTable* Application::documentTreeNodePropertiesProviderTable() const -{ - return &(d->m_documentTreeNodePropertiesProviderTable); -} - void Application::addTranslator(Application::Translator fn) { if (fn) @@ -225,7 +214,7 @@ void Application::NewDocument(const TCollection_ExtendedString&, Handle(TDocStd_ // TODO: check format == "mayo" if not throw exception // Extended from TDocStd_Application::NewDocument() implementation, ensure that in future // OpenCascade versions this code is still compatible! - DocumentPtr newDoc = new Document; + DocumentPtr newDoc = new Document(this); CDF_Application::Open(newDoc); // Add the document in the session this->addDocument(newDoc); outDocument = newDoc; @@ -245,7 +234,6 @@ Application::Application() : QObject(nullptr), d(new Private) { - d->m_settings.setParent(this); } void Application::notifyDocumentAboutToClose(Document::Identifier docIdent) @@ -286,7 +274,7 @@ Application::DocumentIterator::DocumentIterator(const ApplicationPtr& app) { } -Application::DocumentIterator::DocumentIterator(const Application* app) +Application::DocumentIterator::DocumentIterator([[maybe_unused]] const Application* app) #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) : CDF_DirectoryIterator(app->myDirectory) #else diff --git a/src/base/application.h b/src/base/application.h index 24312e32..2f09f002 100644 --- a/src/base/application.h +++ b/src/base/application.h @@ -17,11 +17,6 @@ namespace Mayo { -class Settings; -class DocumentTreeNodePropertiesProviderTable; - -namespace IO { class System; } - // Provides management of Document objects class Application : public QObject, public TDocStd_Application { Q_OBJECT @@ -53,10 +48,6 @@ class Application : public QObject, public TDocStd_Application { void closeDocument(const DocumentPtr& doc); - Settings* settings() const; - IO::System* ioSystem() const; - DocumentTreeNodePropertiesProviderTable* documentTreeNodePropertiesProviderTable() const; - // Provides internationalization support for text output // 1st arg: message to be translated(TextId = context+key) // 2nd arg: when != -1 used to choose an appropriate form for the translation(e.g. "%n file found" vs. "%n files found") diff --git a/src/base/cpp_utils.h b/src/base/cpp_utils.h index f54587ac..0ffa8dc8 100644 --- a/src/base/cpp_utils.h +++ b/src/base/cpp_utils.h @@ -6,28 +6,137 @@ #pragma once +#include +#include #include #include +#include +#ifndef __cpp_lib_integer_comparison_functions +# include +# include +#endif namespace Mayo { -class CppUtils { -public: - static const std::string& nullString() { - static std::string str; - return str; - } +namespace CppUtils { - template - static VALUE findValue(const KEY& key, const std::unordered_map& hashmap) { - auto it = hashmap.find(key); - const VALUE defaultValue = {}; - return it != hashmap.cend() ? it->second : defaultValue; - } +inline const std::string& nullString() +{ + static std::string str; + return str; +} + +template +VALUE findValue(const KEY& key, const std::unordered_map& hashmap) { + auto it = hashmap.find(key); + const VALUE defaultValue = {}; + return it != hashmap.cend() ? it->second : defaultValue; +} + +inline void toggle(bool& value) +{ + value = !value; +} + +// Implementation from https://en.cppreference.com/w/cpp/utility/intcmp +template constexpr bool cmpEqual(T t, U u) noexcept +{ +#ifdef __cpp_lib_integer_comparison_functions + return std::cmp_equal(t, u); +#else + using UT = std::make_unsigned_t; + using UU = std::make_unsigned_t; + if constexpr (std::is_signed_v == std::is_signed_v) + return t == u; + else if constexpr (std::is_signed_v) + return t < 0 ? false : UT(t) == u; + else + return u < 0 ? false : t == UU(u); +#endif +} + +// Implementation from https://en.cppreference.com/w/cpp/utility/intcmp +template constexpr bool cmpNotEqual(T t, U u) noexcept +{ +#ifdef __cpp_lib_integer_comparison_functions + return std::cmp_not_equal(t, u); +#else + return !cmpEqual(t, u); +#endif +} - static void toggle(bool& value) { - value = !value; +// Implementation from https://en.cppreference.com/w/cpp/utility/intcmp +template constexpr bool cmpLess(T t, U u) noexcept +{ +#ifdef __cpp_lib_integer_comparison_functions + return std::cmp_less(t, u); +#else + using UT = std::make_unsigned_t; + using UU = std::make_unsigned_t; + if constexpr (std::is_signed_v == std::is_signed_v) + return t < u; + else if constexpr (std::is_signed_v) + return t < 0 ? true : UT(t) < u; + else + return u < 0 ? false : t < UU(u); +#endif +} + +// Implementation from https://en.cppreference.com/w/cpp/utility/intcmp +template constexpr bool cmpGreater(T t, U u) noexcept +{ +#ifdef __cpp_lib_integer_comparison_functions + return std::cmp_greater(t, u); +#else + return cmpLess(u, t); +#endif +} + +// Implementation from https://en.cppreference.com/w/cpp/utility/intcmp +template constexpr bool cmpLessEqual(T t, U u) noexcept +{ +#ifdef __cpp_lib_integer_comparison_functions + return std::cmp_less_equal(t, u); +#else + return !cmpGreater(t, u); +#endif +} + +// Implementation from https://en.cppreference.com/w/cpp/utility/intcmp +template constexpr bool cmpGreaterEqual(T t, U u) noexcept +{ +#ifdef __cpp_lib_integer_comparison_functions + return std::cmp_greater_equal(t, u); +#else + return !cmpLess(t, u); +#endif +} + +// Implementation from https://en.cppreference.com/w/cpp/utility/in_range +template constexpr bool inRange(T t) noexcept +{ +#ifdef __cpp_lib_integer_comparison_functions + return std::in_range(t, u); +#else + return cmpGreaterEqual(t, std::numeric_limits::lowest()) + && cmpLessEqual(t, std::numeric_limits::max()); +#endif +} + +// Throws object of specified error type if 'condition' is met +template void throwErrorIf(bool condition, ERROR_ARGS... args) +{ + if (condition) { + throw ERROR(args...); } -}; +} + +// 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"); + return static_cast(t); +} +} // namespace CppUtils } // namespace Mayo diff --git a/src/base/data_triangulation.cpp b/src/base/data_triangulation.cpp new file mode 100644 index 00000000..fb76d17b --- /dev/null +++ b/src/base/data_triangulation.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "data_triangulation.h" + +#include +#include +#include +#include + +namespace Mayo { + +const Standard_GUID& DataTriangulation::GetID() +{ + static const Standard_GUID DataTriangulationID("3020FF98-2B49-4E0B-A414-949A534F24F7"); + return DataTriangulationID; +} + +DataTriangulationPtr DataTriangulation::Set(const TDF_Label& label) +{ + DataTriangulationPtr dataMesh; + if (!label.FindAttribute(DataTriangulation::GetID(), dataMesh)) { + dataMesh = new DataTriangulation; + label.AddAttribute(dataMesh); + } + + return dataMesh; +} + +DataTriangulationPtr DataTriangulation::Set(const TDF_Label& label, const Handle(Poly_Triangulation)& mesh) +{ + DataTriangulationPtr dataMesh = DataTriangulation::Set(label); + dataMesh->TDataXtd_Triangulation::Set(mesh); + return dataMesh; +} + +DataTriangulationPtr DataTriangulation::Set( + const TDF_Label& label, const Handle(Poly_Triangulation)& mesh, Span spanNodeColor) +{ + DataTriangulationPtr dataMesh = DataTriangulation::Set(label, mesh); + dataMesh->copyNodeColors(spanNodeColor); + return dataMesh; +} + +const Standard_GUID& DataTriangulation::ID() const +{ + return DataTriangulation::GetID(); +} + +void DataTriangulation::Restore(const Handle(TDF_Attribute)& attribute) +{ + TDataXtd_Triangulation::Restore(attribute); + auto dataMesh = DataTriangulationPtr::DownCast(attribute); + if (dataMesh) + this->copyNodeColors(dataMesh->m_vecNodeColor); +} + +Handle(TDF_Attribute) DataTriangulation::NewEmpty() const +{ + return new DataTriangulation; +} + +void DataTriangulation::Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)& table) const +{ + TDataXtd_Triangulation::Paste(into, table); + auto dataMesh = DataTriangulationPtr::DownCast(into); + if (dataMesh) + dataMesh->copyNodeColors(m_vecNodeColor); +} + +Standard_OStream& DataTriangulation::Dump(Standard_OStream& ostr) const +{ + ostr << "DataTriangulation -- "; + TDataXtd_Triangulation::Dump(ostr); + return ostr; +} + +void Mayo::DataTriangulation::copyNodeColors(Span spanNodeColor) +{ + m_vecNodeColor.clear(); + std::copy(spanNodeColor.begin(), spanNodeColor.end(), std::back_inserter(m_vecNodeColor)); +} + +} // namespace Mayo diff --git a/src/base/data_triangulation.h b/src/base/data_triangulation.h new file mode 100644 index 00000000..66422bc2 --- /dev/null +++ b/src/base/data_triangulation.h @@ -0,0 +1,49 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "span.h" + +#include +#include +#include + +namespace Mayo { + +class DataTriangulation; +DEFINE_STANDARD_HANDLE(DataTriangulation, TDataXtd_Triangulation) +using DataTriangulationPtr = Handle(DataTriangulation); + +class DataTriangulation : public TDataXtd_Triangulation { +public: + static const Standard_GUID& GetID(); + static DataTriangulationPtr Set(const TDF_Label& label); + static DataTriangulationPtr Set(const TDF_Label& label, const Handle(Poly_Triangulation)& mesh); + static DataTriangulationPtr Set( + const TDF_Label& label, + const Handle(Poly_Triangulation)& mesh, + Span spanNodeColor + ); + + Span nodeColors() const { return m_vecNodeColor; } + + // -- from TDF_Attribute + const Standard_GUID& ID() const override; + void Restore(const Handle(TDF_Attribute)& attribute) override; + Handle(TDF_Attribute) NewEmpty() const override; + void Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)& table) const override; + Standard_OStream& Dump(Standard_OStream& ostr) const override; + + DEFINE_STANDARD_RTTI_INLINE(DataTriangulation, TDataXtd_Triangulation) + +private: + void copyNodeColors(Span spanNodeColor); + + std::vector m_vecNodeColor; +}; + +} // namespace Mayo diff --git a/src/base/document.cpp b/src/base/document.cpp index b319a597..aee14dd2 100644 --- a/src/base/document.cpp +++ b/src/base/document.cpp @@ -4,19 +4,21 @@ ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ +#include "document.h" + #include "application.h" #include "caf_utils.h" -#include "document.h" +#include "cpp_utils.h" #include #include #include -#include namespace Mayo { -Document::Document() - : QObject(nullptr), - TDocStd_Document(NameFormatBinary) +Document::Document(const ApplicationPtr& app) + : QObject(app.get()), + TDocStd_Document(NameFormatBinary), + m_app(app) { TDF_TagSource::Set(this->rootLabel()); } @@ -79,7 +81,7 @@ bool Document::isEntity(TreeNodeId nodeId) int Document::entityCount() const { - return m_modelTree.roots().size(); + return CppUtils::safeStaticCast(m_modelTree.roots().size()); } TDF_Label Document::entityLabel(int index) const @@ -172,7 +174,8 @@ void Document::destroyEntity(TreeNodeId entityTreeNodeId) void Document::BeforeClose() { TDocStd_Document::BeforeClose(); - Application::instance()->notifyDocumentAboutToClose(m_identifier); + if (m_app) + m_app->notifyDocumentAboutToClose(m_identifier); } void Document::ChangeStorageFormat(const TCollection_ExtendedString& newStorageFormat) diff --git a/src/base/document.h b/src/base/document.h index 62ed8036..52d554ee 100644 --- a/src/base/document.h +++ b/src/base/document.h @@ -6,6 +6,7 @@ #pragma once +#include "application_ptr.h" #include "document_ptr.h" #include "document_tree_node.h" #include "filepath.h" @@ -18,9 +19,6 @@ namespace Mayo { -class Application; -class DocumentTreeNode; - class Document : public QObject, public TDocStd_Document { Q_OBJECT Q_PROPERTY(int identifier READ identifier) @@ -79,17 +77,16 @@ class Document : public QObject, public TDocStd_Document { DEFINE_STANDARD_RTTI_INLINE(Document, TDocStd_Document) private: + Document(const ApplicationPtr& app); + friend class Application; class FormatBinaryRetrievalDriver; class FormatXmlRetrievalDriver; - friend class XCafScopeImport; - friend class SingleScopeImport; - - Document(); void initXCaf(); void setIdentifier(Identifier ident) { m_identifier = ident; } + ApplicationPtr m_app; Identifier m_identifier = -1; std::string m_name; FilePath m_filePath; diff --git a/src/base/document_tree_node.cpp b/src/base/document_tree_node.cpp index 4a9d6c75..e51a23ac 100644 --- a/src/base/document_tree_node.cpp +++ b/src/base/document_tree_node.cpp @@ -37,6 +37,14 @@ bool DocumentTreeNode::isEntity() const return this->isValid() ? m_document->isEntity(m_id) : false; } +bool DocumentTreeNode::isLeaf() const +{ + if (this->isValid()) + return m_document->modelTree().nodeIsLeaf(m_id); + else + return false; +} + bool DocumentTreeNode::operator==(const DocumentTreeNode& other) const { if (!this->isValid() || !other.isValid()) diff --git a/src/base/document_tree_node.h b/src/base/document_tree_node.h index 1bda25b3..3f6de7fe 100644 --- a/src/base/document_tree_node.h +++ b/src/base/document_tree_node.h @@ -21,6 +21,7 @@ class DocumentTreeNode { TDF_Label label() const; bool isEntity() const; + bool isLeaf() const; const DocumentPtr& document() const { return m_document; } TreeNodeId id() const { return m_id; } diff --git a/src/base/document_tree_node_properties_provider.cpp b/src/base/document_tree_node_properties_provider.cpp index 04f23e8a..d1fa60d1 100644 --- a/src/base/document_tree_node_properties_provider.cpp +++ b/src/base/document_tree_node_properties_provider.cpp @@ -5,24 +5,7 @@ ****************************************************************************/ #include "document_tree_node_properties_provider.h" -#include "document_tree_node.h" namespace Mayo { -void DocumentTreeNodePropertiesProviderTable::addProvider(ProviderPtr provider) -{ - m_vecProvider.push_back(std::move(provider)); -} - -std::unique_ptr -DocumentTreeNodePropertiesProviderTable::properties(const DocumentTreeNode& treeNode) const -{ - for (const ProviderPtr& provider : m_vecProvider) { - if (provider->supports(treeNode)) - return provider->properties(treeNode); - } - - return std::unique_ptr(); -} - } // namespace Mayo diff --git a/src/base/document_tree_node_properties_provider.h b/src/base/document_tree_node_properties_provider.h index 5941d797..f88d1dea 100644 --- a/src/base/document_tree_node_properties_provider.h +++ b/src/base/document_tree_node_properties_provider.h @@ -21,17 +21,4 @@ class DocumentTreeNodePropertiesProvider { virtual std::unique_ptr properties(const DocumentTreeNode& treeNode) const = 0; }; -class DocumentTreeNodePropertiesProviderTable { -public: - using ProviderPtr = std::unique_ptr; - - void addProvider(ProviderPtr provider); - Span providers() const { return m_vecProvider; } - - std::unique_ptr properties(const DocumentTreeNode& treeNode) const; - -private: - std::vector m_vecProvider; -}; - } // namespace Mayo diff --git a/src/base/enumeration.cpp b/src/base/enumeration.cpp index abf55b87..1b1a4f97 100644 --- a/src/base/enumeration.cpp +++ b/src/base/enumeration.cpp @@ -6,8 +6,12 @@ #include "enumeration.h" +#include "cpp_utils.h" + +#include #include #include +#include namespace Mayo { @@ -34,50 +38,29 @@ Enumeration& Enumeration::changeTrContext(std::string_view context) return *this; } -const Enumeration::Item& Enumeration::findItem(Enumeration::Value value) const +int Enumeration::findIndexByValue_untyped(Value value) const { - const int index = this->findIndex(value); - Expects(index != -1); - return this->itemAt(index); + auto it = std::find_if(m_vecItem.cbegin(), m_vecItem.cend(), [=](const Item& item) { + return item.value == value; + }); + return it != m_vecItem.cend() ? CppUtils::safeStaticCast(it - m_vecItem.cbegin()) : -1; } -int Enumeration::findIndex(Value value) const +Enumeration::Value Enumeration::findValueByName(std::string_view name) const { - auto it = std::find_if( - m_vecItem.cbegin(), - m_vecItem.cend(), - [=](const Item& item) { return item.value == value; }); - return it != m_vecItem.cend() ? it - m_vecItem.cbegin() : -1; -} + const Enumeration::Item* ptrItem = this->findItemByName(name); + if (!ptrItem) + throw std::runtime_error(fmt::format("No matching enumeration item found [name={}]", name)); -Enumeration::Value Enumeration::findValue(std::string_view name) const -{ - const Enumeration::Item* ptrItem = this->findItem(name); - assert(ptrItem != nullptr); - return ptrItem ? ptrItem->value : -1; + return ptrItem->value; } bool Enumeration::contains(std::string_view name) const { - return this->findItem(name) != nullptr; -} - -const Enumeration& Enumeration::null() -{ - static const Enumeration null; - return null; -} - -std::string_view Enumeration::findName(Value value) const -{ - const int index = this->findIndex(value); - if (index != -1) - return this->itemAt(index).name.key; - - return {}; + return this->findItemByName(name) != nullptr; } -const Enumeration::Item* Enumeration::findItem(std::string_view name) const +const Enumeration::Item* Enumeration::findItemByName(std::string_view name) const { auto itFound = std::find_if( m_vecItem.cbegin(), diff --git a/src/base/enumeration.h b/src/base/enumeration.h index 1e51e736..86e81e20 100644 --- a/src/base/enumeration.h +++ b/src/base/enumeration.h @@ -10,45 +10,72 @@ #include "text_id.h" #include #include +#include namespace Mayo { +// Provides meta-data about enumerated values class Enumeration { public: + // Common type to store enumerated values using Value = int; + + // Enumeration item being a value-name pair struct Item { Value value; TextId name; }; + // Ctors Enumeration() = default; Enumeration(std::initializer_list listItem); + // Adds an enumerated item template Enumeration& addItem(VALUE value, const TextId& name); + // Iterates over name of items and removes 'prefix' string that may appear at the beginning Enumeration& chopPrefix(std::string_view prefix); + + // Assigns 'context' to TextId::trContext of all Item objects Enumeration& changeTrContext(std::string_view context); + // Count of enumeration items int size() const { return int(m_vecItem.size()); } - bool empty() const { return this->size() == 0; } + bool empty() const { return m_vecItem.empty(); } + + // Finds index of the item corresponding to an enumerated value. Returns -1 if not found + template int findIndexByValue(ENUM value) const; + + // Finds the item corresponding to an enumerated value. Returns nullptr if not found + template const Item* findItemByValue(ENUM value) const; + + // Finds the item corresponding to a name. Returns nullptr if not found + const Item* findItemByName(std::string_view name) const; - const Item& findItem(Value value) const; - int findIndex(Value value) const; - std::string_view findName(Value value) const; - Value findValue(std::string_view name) const; + // Finds the name of an enumerated value. Returns empty string if not found + template std::string_view findNameByValue(ENUM value) const; + + // Finds the enumerated value corresponding to a name. Throws exception if not found + Value findValueByName(std::string_view name) const; + + // Returns 'true' if there is an item matching 'name' bool contains(std::string_view name) const; + // Returns item at 'index' const Item& itemAt(int index) const { return m_vecItem.at(index); } + + // Returns read-only array of the items Span items() const { return m_vecItem; } - const Item* findItem(std::string_view name) const; + // Creates an Enumeration object from an enumerated type, using MetaEnum helper + // Note: client code has to include header "enumeration_fromenum.h" template static Enumeration fromType(); - static const Enumeration& null(); - private: + int findIndexByValue_untyped(Value value) const; + std::vector m_vecItem; }; @@ -63,4 +90,26 @@ template Enumeration& Enumeration::addItem(VALUE value, const Te return *this; } +template int Enumeration::findIndexByValue(ENUM value) const +{ + static_assert(std::is_enum_v || std::is_integral_v, "ENUM must be an enumeration or integer type"); + return this->findIndexByValue_untyped(static_cast(value)); +} + +template const Enumeration::Item* Enumeration::findItemByValue(ENUM value) const +{ + static_assert(std::is_enum_v || std::is_integral_v, "ENUM must be an enumeration or integer type"); + const int index = this->findIndexByValue_untyped(static_cast(value)); + return index != -1 ? &(this->itemAt(index)) : nullptr; +} + +template std::string_view Enumeration::findNameByValue(ENUM value) const +{ + const int index = this->findIndexByValue(value); + if (index != -1) + return this->itemAt(index).name.key; + else + return {}; +} + } // namespace Mayo diff --git a/src/base/filepath.h b/src/base/filepath.h index ab5c8c58..0e2803cc 100644 --- a/src/base/filepath.h +++ b/src/base/filepath.h @@ -15,6 +15,21 @@ namespace Mayo { using FilePath = std::filesystem::path; +// Exception-safe version of std::filesystem::file_size() +inline uintmax_t filepathFileSize(const FilePath& fp) { + std::error_code ec; + return std::filesystem::file_size(fp, ec); +} + +// Exception-safe version of std::filesystem::canonical() +inline FilePath filepathCanonical(const FilePath& fp) { + try { + return std::filesystem::canonical(fp); + } catch (...) { // fs::canonical() might throw on non-existing files + return fp; + } +} + // Exception-safe version of std::filesystem::equivalent() inline bool filepathExists(const FilePath& fp) { try { diff --git a/src/base/io_format.cpp b/src/base/io_format.cpp index ffc75fe6..c4f21d72 100644 --- a/src/base/io_format.cpp +++ b/src/base/io_format.cpp @@ -16,15 +16,16 @@ std::string_view formatIdentifier(Format format) switch (format) { case Format_Unknown: return ""; case Format_Image: return "Image"; - case Format_STEP: return "STEP"; - case Format_IGES: return "IGES"; + case Format_STEP: return "STEP"; + case Format_IGES: return "IGES"; case Format_OCCBREP: return "OCCBREP"; - case Format_STL: return "STL"; - case Format_OBJ: return "OBJ"; - case Format_GLTF: return "GLTF"; - case Format_VRML: return "VRML"; - case Format_AMF: return "AMF"; - case Format_DXF: return "DXF"; + case Format_STL: return "STL"; + case Format_OBJ: return "OBJ"; + case Format_GLTF: return "GLTF"; + case Format_VRML: return "VRML"; + case Format_AMF: return "AMF"; + case Format_DXF: return "DXF"; + case Format_PLY: return "PLY"; } return ""; @@ -35,15 +36,16 @@ std::string_view formatName(Format format) switch (format) { case Format_Unknown: return "Format_Unknown"; case Format_Image: return "Image"; - case Format_STEP: return "STEP(ISO 10303)"; - case Format_IGES: return "IGES(ASME Y14.26M)"; + case Format_STEP: return "STEP(ISO 10303)"; + case Format_IGES: return "IGES(ASME Y14.26M)"; case Format_OCCBREP: return "OpenCascade BREP"; - case Format_STL: return "STL(STereo-Lithography)"; - case Format_OBJ: return "Wavefront OBJ"; - case Format_GLTF: return "glTF(GL Transmission Format)"; - case Format_VRML: return "VRML(ISO/CEI 14772-2)"; - case Format_AMF: return "Additive manufacturing file format(ISO/ASTM 52915:2016)"; - case Format_DXF: return "Drawing Exchange Format"; + case Format_STL: return "STL(STereo-Lithography)"; + case Format_OBJ: return "Wavefront OBJ"; + case Format_GLTF: return "glTF(GL Transmission Format)"; + case Format_VRML: return "VRML(ISO/CEI 14772-2)"; + case Format_AMF: return "Additive manufacturing file format(ISO/ASTM 52915:2016)"; + case Format_DXF: return "Drawing Exchange Format"; + case Format_PLY: return "Polygon File Format"; } return ""; @@ -51,29 +53,31 @@ 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 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 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 amf_suffix[] = { "amf" }; + static std::string_view dxf_suffix[] = { "dxf" }; + static std::string_view ply_suffix[] = { "ply" }; 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_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_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; } return {}; diff --git a/src/base/io_format.h b/src/base/io_format.h index 478b5495..4687cf5e 100644 --- a/src/base/io_format.h +++ b/src/base/io_format.h @@ -24,7 +24,8 @@ enum Format { Format_GLTF, Format_VRML, Format_AMF, - Format_DXF + Format_DXF, + Format_PLY }; // Returns identifier(unique short name) corresponding to 'format' diff --git a/src/base/io_reader.cpp b/src/base/io_reader.cpp index 83f407ef..b5ba30d3 100644 --- a/src/base/io_reader.cpp +++ b/src/base/io_reader.cpp @@ -5,23 +5,9 @@ ****************************************************************************/ #include "io_reader.h" -#include "messenger.h" namespace Mayo { namespace IO { -Reader::Reader() - : m_messenger(NullMessenger::instance()) -{ -} - -void Reader::setMessenger(Messenger* messenger) -{ - if (messenger) - m_messenger = messenger; - else - m_messenger = NullMessenger::instance(); -} - } // namespace IO } // namespace Mayo diff --git a/src/base/io_reader.h b/src/base/io_reader.h index 4751c120..971eda24 100644 --- a/src/base/io_reader.h +++ b/src/base/io_reader.h @@ -9,39 +9,51 @@ #include "document_ptr.h" #include "filepath.h" #include "io_format.h" +#include "messenger_client.h" #include "span.h" #include #include namespace Mayo { -class Messenger; class PropertyGroup; class TaskProgress; namespace IO { -class Reader { +// Abstract base class for readers +// Provides services for reading files in two steps: +// - parse input file into memory(service Reader::readFile()) +// - convert data in memory into Document object(service Reader::transfer()) +class Reader : public MessengerClient { public: - Reader(); virtual ~Reader() = default; + // Reads file at path 'fp' into memory using indicator to report progress + // Returns 'true' on success virtual bool readFile(const FilePath& fp, TaskProgress* progress) = 0; - virtual TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) = 0; - virtual void applyProperties(const PropertyGroup* /*params*/) {} - Messenger* messenger() const { return m_messenger; } - void setMessenger(Messenger* messenger); + // Converts data read during readFile() step into document 'doc' using indicator to report progress + // Returns the list of entities added to document 'doc' + virtual TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) = 0; -private: - Messenger* m_messenger = nullptr; + // Apply properties contain in 'group' to the reader's parameter values(known in reader sub-class) + virtual void applyProperties(const PropertyGroup* group) = 0; }; +// Abstract base class for all reader factories class FactoryReader { public: virtual ~FactoryReader() = default; + + // Returns supported formats, ie the formats this factory can create readers for virtual Span formats() const = 0; + + // Creates and returns a Reader object that matches the given format, or nullptr if no matching reader is found virtual std::unique_ptr create(Format format) const = 0; + + // Creates and returns properties that match the given format. Those properties is a generic + // way to change parameter values of a Reader object corresponding to format(see also Reader::applyProperties()) virtual std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const = 0; }; diff --git a/src/base/io_system.cpp b/src/base/io_system.cpp index f746f048..86ac965b 100644 --- a/src/base/io_system.cpp +++ b/src/base/io_system.cpp @@ -6,6 +6,8 @@ #include "io_system.h" +#include "caf_utils.h" +#include "cpp_utils.h" #include "document.h" #include "io_parameters_provider.h" #include "io_reader.h" @@ -13,6 +15,7 @@ #include "messenger.h" #include "task_manager.h" #include "task_progress.h" +#include "tkernel_utils.h" #include #include @@ -22,6 +25,7 @@ #include #include #include +#include #include namespace Mayo { @@ -29,17 +33,6 @@ namespace IO { namespace { -TaskProgress* nullTaskProgress() -{ - static TaskProgress null; - return &null; -} - -Messenger* nullMessenger() -{ - return NullMessenger::instance(); -} - bool containsFormat(Span spanFormat, Format format) { auto itFormat = std::find(spanFormat.begin(), spanFormat.end(), format); @@ -63,7 +56,7 @@ Format System::probeFormat(const FilePath& filepath) const file.read(buff.data(), buff.size()); FormatProbeInput probeInput = {}; probeInput.filepath = filepath; - probeInput.contentsBegin = std::string_view(buff.data(), buff.size()); + probeInput.contentsBegin = std::string_view(buff.data(), file.gcount()); probeInput.hintFullSize = std::filesystem::file_size(filepath); for (const FormatProbe& fnProbe : m_vecFormatProbe) { const Format format = fnProbe(probeInput); @@ -187,8 +180,8 @@ bool System::importInDocument(const Args_ImportInDocument& args) DocumentPtr doc = args.targetDocument; const auto listFilepath = args.filepaths; - TaskProgress* rootProgress = args.progress ? args.progress : nullTaskProgress(); - Messenger* messenger = args.messenger ? args.messenger : nullMessenger(); + TaskProgress* rootProgress = args.progress ? args.progress : &TaskProgress::null(); + Messenger* messenger = args.messenger ? args.messenger : &Messenger::null(); bool ok = true; @@ -309,7 +302,7 @@ bool System::importInDocument(const Args_ImportInDocument& args) childTaskManager.run(taskData.taskId, TaskAutoDestroy::Off); // Transfer to document - int taskDataCount = vecTaskData.size(); + auto taskDataCount = CppUtils::safeStaticCast(vecTaskData.size()); while (taskDataCount > 0 && !rootProgress->isAbortRequested()) { auto it = std::find_if(vecTaskData.begin(), vecTaskData.end(), [&](const TaskData& taskData) { return !taskData.transferred && childTaskManager.waitForDone(taskData.taskId, 25); @@ -336,8 +329,8 @@ System::Operation_ImportInDocument System::importInDocument() { bool System::exportApplicationItems(const Args_ExportApplicationItems& args) { - TaskProgress* progress = args.progress ? args.progress : nullTaskProgress(); - Messenger* messenger = args.messenger ? args.messenger : nullMessenger(); + TaskProgress* progress = args.progress ? args.progress : &TaskProgress::null(); + Messenger* messenger = args.messenger ? args.messenger : &Messenger::null(); auto fnError = [=](std::string_view errorMsg) { const std::string strFilepath = args.targetFilepath.u8string(); messenger->emitError(fmt::format(textIdTr("Error during export to '{}'\n{}"), strFilepath, errorMsg)); @@ -417,6 +410,50 @@ System::Operation_ExportApplicationItems System::exportApplicationItems() return Operation_ExportApplicationItems(*this); } +void System::visitUniqueItems( + Span spanItem, + std::function fnCallback) +{ + std::unordered_set setDoc; + for (const ApplicationItem& item : spanItem) { + if (item.isDocument()) { + auto [it, ok] = setDoc.insert(item.document()); + if (ok) + fnCallback(item); + } + } + + std::unordered_set setNode; + for (const ApplicationItem& item : spanItem) { + if (item.isDocumentTreeNode()) { + auto itDoc = setDoc.find(item.document()); + if (itDoc == setDoc.cend()) { + auto [it, ok] = setNode.insert(item.documentTreeNode().label()); + if (ok) + fnCallback(item); + } + } + } +} + +void System::traverseUniqueItems( + Span spanItem, + std::function fnCallback, + TreeTraversal mode) +{ + System::visitUniqueItems(spanItem, [=](const ApplicationItem& item) { + const DocumentPtr doc = item.document(); + const Tree& modelTree = doc->modelTree(); + if (item.isDocument()) { + traverseTree(modelTree, [&](TreeNodeId id) { fnCallback({ doc, id }); }, mode); + } + else if (item.isDocumentTreeNode()) { + const TreeNodeId docTreeNodeId = item.documentTreeNode().id(); + traverseTree(docTreeNodeId, modelTree, [&](TreeNodeId id) { fnCallback({ doc, id }); }, mode); + } + }); +} + System::Operation_ImportInDocument& System::Operation_ImportInDocument::targetDocument(const DocumentPtr& document) { m_args.targetDocument = document; @@ -486,70 +523,29 @@ System::Operation_ImportInDocument::Operation_ImportInDocument(System& system) namespace { -bool isSpace(char c) { - return std::isspace(c, std::locale::classic()); -} - -bool matchToken(std::string_view::const_iterator itBegin, std::string_view token) { - return std::strncmp(&(*itBegin), token.data(), token.size()) == 0; -} - -auto findFirstNonSpace(std::string_view str) { - return std::find_if_not(str.cbegin(), str.cend(), isSpace); +bool matchRegExp(std::string_view str, const std::regex& rx) +{ + return std::regex_search(str.cbegin(), str.cend(), rx); } } // namespace Format probeFormat_STEP(const System::FormatProbeInput& input) { - std::string_view sample = input.contentsBegin; - // regex : ^\s*ISO-10303-21\s*;\s*HEADER - constexpr std::string_view stepIsoId = "ISO-10303-21"; - constexpr std::string_view stepHeaderToken = "HEADER"; - auto itContentsBegin = findFirstNonSpace(sample); - if (matchToken(itContentsBegin, stepIsoId)) { - auto itChar = std::find_if_not(itContentsBegin + stepIsoId.size(), sample.cend(), isSpace); - if (itChar != sample.cend() && *itChar == ';') { - itChar = std::find_if_not(itChar + 1, sample.cend(), isSpace); - if (matchToken(itChar, stepHeaderToken)) - return Format_STEP; - } - } - - return Format_Unknown; + const std::regex rx{ R"(^\s*ISO-10303-21\s*;\s*HEADER)" }; + return matchRegExp(input.contentsBegin, rx) ? Format_STEP : Format_Unknown; } Format probeFormat_IGES(const System::FormatProbeInput& input) { - std::string_view sample = input.contentsBegin; - // regex : ^.{72}S\s*[0-9]+\s*[\n\r\f] - bool isIges = true; - if (sample.size() >= 80 && sample[72] == 'S') { - for (int i = 73; i < 80 && isIges; ++i) { - if (sample[i] != ' ' && !std::isdigit(static_cast(sample[i]))) - isIges = false; - } - - const char c80 = sample[80]; - if (isIges && (c80 == '\n' || c80 == '\r' || c80 == '\f')) { - const int sVal = std::atoi(sample.data() + 73); - if (sVal == 1) - return Format_IGES; - } - } - - return Format_Unknown; + const std::regex rx{ R"(^.{72}S\s*[0-9]+\s*[\n\r\f])" }; + return matchRegExp(input.contentsBegin, rx) ? Format_IGES : Format_Unknown; } Format probeFormat_OCCBREP(const System::FormatProbeInput& input) { - // regex : ^\s*DBRep_DrawableShape - auto itContentsBegin = findFirstNonSpace(input.contentsBegin); - constexpr std::string_view occBRepToken = "DBRep_DrawableShape"; - if (matchToken(itContentsBegin, occBRepToken)) - return Format_OCCBREP; - - return Format_Unknown; + const std::regex rx{ R"(^\s*DBRep_DrawableShape)" }; + return matchRegExp(input.contentsBegin, rx) ? Format_OCCBREP : Format_Unknown; } Format probeFormat_STL(const System::FormatProbeInput& input) @@ -574,10 +570,8 @@ Format probeFormat_STL(const System::FormatProbeInput& input) // ASCII STL ? { - // regex : ^\s*solid - constexpr std::string_view asciiStlToken = "solid"; - auto itContentsBegin = findFirstNonSpace(input.contentsBegin); - if (matchToken(itContentsBegin, asciiStlToken)) + const std::regex rx{ R"(^\s*solid)" }; + if (matchRegExp(input.contentsBegin, rx)) return Format_STL; } @@ -586,12 +580,14 @@ Format probeFormat_STL(const System::FormatProbeInput& input) Format probeFormat_OBJ(const System::FormatProbeInput& input) { - std::string_view sample = input.contentsBegin; - const std::regex rx{ R"("^\s*(v|vt|vn|vp|surf)\s+[-\+]?[0-9\.]+\s")" }; - if (std::regex_search(sample.cbegin(), sample.cend(), rx)) - return Format_OBJ; + const std::regex rx{ R"([^\n]\s*(v|vt|vn|vp|surf)\s+[-\+]?[0-9\.]+\s)" }; + return matchRegExp(input.contentsBegin, rx) ? Format_OBJ : Format_Unknown; +} - return Format_Unknown; +Format probeFormat_PLY(const System::FormatProbeInput& input) +{ + const std::regex rx{ R"(^\s*ply\s+format\s+(ascii|binary_little_endian|binary_big_endian)\s+)" }; + return matchRegExp(input.contentsBegin, rx) ? Format_PLY : Format_Unknown; } void addPredefinedFormatProbes(System* system) @@ -604,6 +600,7 @@ void addPredefinedFormatProbes(System* system) system->addFormatProbe(probeFormat_OCCBREP); system->addFormatProbe(probeFormat_STL); system->addFormatProbe(probeFormat_OBJ); + system->addFormatProbe(probeFormat_PLY); } } // namespace IO diff --git a/src/base/io_system.h b/src/base/io_system.h index b707a610..cde76a39 100644 --- a/src/base/io_system.h +++ b/src/base/io_system.h @@ -11,6 +11,7 @@ #include "io_format.h" #include "io_reader.h" #include "io_writer.h" +#include "libtree.h" #include "property.h" #include "span.h" #include "text_id.h" @@ -57,35 +58,78 @@ class System { Span readerFormats() const { return m_vecReaderFormat; } Span writerFormats() const { return m_vecWriterFormat; } + // // Import service + // + // Contains arguments for the importInDocument() function struct Args_ImportInDocument { + // Target document where entities read from `filepaths` will be imported DocumentPtr targetDocument; - Span filepaths; // The files to be imported in target document + + // List of files to be imported in target document + Span filepaths; + + // Optional: provider of format-specific parameters to be considered when reading const ParametersProvider* parametersProvider = nullptr; + + // Optional: function applied to each imported entity. Executed before adding entities into + // target document + // 1st arg: CAF label of the entity to "post-process" + // 2nd arg: progress indicator of the post-process function std::function entityPostProcess; + + // Optional: predicate telling whether imported entities have to be post-processed(ie whether + // `entityPostProcess` function has to be called) + // The single argument being the format of the file from which entities were read std::function entityPostProcessRequiredIf; + + // Optional: progress size(eg 25%) of the whole post-process operation int entityPostProcessProgressSize = 0; + + // Optional: title of the whole post-process operation std::string entityPostProcessProgressStep; + + // Optional: the messenger object used to report any additional infos, warnings and errors Messenger* messenger = nullptr; + + // Optional: the indicator object used to report progress of the import operation TaskProgress* progress = nullptr; }; bool importInDocument(const Args_ImportInDocument& args); + // // Export service + // + // Contains arguments for the exportApplicationItems() function struct Args_ExportApplicationItems { + // List of items to be exported Span applicationItems; + + // Path to the target file where items will be written FilePath targetFilepath; + + // Format in which items are exported Format targetFormat = Format_Unknown; - const PropertyGroup* parameters = nullptr; + + // Optional: format-specific parameters to be considered when writting items + const PropertyGroup* parameters = nullptr; // TODO use ParametersProvider instead? + + // Optional: the messenger object used to report any additional infos, warnings and errors Messenger* messenger = nullptr; + + // Optional: the indicator object used to report progress of the import operation TaskProgress* progress = nullptr; }; bool exportApplicationItems(const Args_ExportApplicationItems& args); + // // Fluent API: import service + // + // Helper struct to provide fluent-like interface over Args_ImportInDocument + // See also https://en.wikipedia.org/wiki/Fluent_interface struct Operation_ImportInDocument { using Operation = Operation_ImportInDocument; Operation& targetDocument(const DocumentPtr& document); @@ -93,14 +137,13 @@ class System { Operation& withFilepaths(Span filepaths); Operation& withParametersProvider(const ParametersProvider* provider); - // Post-processing executed before adding entities into Document Operation& withEntityPostProcess(std::function fn); Operation& withEntityPostProcessRequiredIf(std::function fn); Operation& withEntityPostProcessInfoProgress(int progressSize, std::string_view progressStep); Operation& withMessenger(Messenger* messenger); Operation& withTaskProgress(TaskProgress* progress); - bool execute(); + bool execute(); // Runs System::importInDocument() function private: friend class System; @@ -110,8 +153,12 @@ class System { }; Operation_ImportInDocument importInDocument(); + // // Fluent API: export service + // + // Helper struct to provide fluent-like interface over Args_ExportApplicationItems + // See also https://en.wikipedia.org/wiki/Fluent_interface struct Operation_ExportApplicationItems { using Operation = Operation_ExportApplicationItems; Operation& targetFile(const FilePath& filepath); @@ -120,7 +167,7 @@ class System { Operation& withParameters(const PropertyGroup* parameters); Operation& withMessenger(Messenger* messenger); Operation& withTaskProgress(TaskProgress* progress); - bool execute(); + bool execute(); // Runs System::exportApplicationItems() function private: friend class System; @@ -130,6 +177,23 @@ class System { }; Operation_ExportApplicationItems exportApplicationItems(); + // Helpers + + // Iterate over `spanItem` and call `fnCallback` for each item. Garantees that doublon items + // will be visited only once + static void visitUniqueItems( + Span spanItem, + std::function fnCallback + ); + + // Iterate over `spanItem` and then deep traverse the corresponding tree node to + // call `fnCallback` for each item. Garantees that doublon items will be visited only once + static void traverseUniqueItems( + Span spanItem, + std::function fnCallback, + TreeTraversal mode = TreeTraversal::PreOrder + ); + // Implementation private: std::vector m_vecFormatProbe; @@ -145,6 +209,7 @@ Format probeFormat_IGES(const System::FormatProbeInput& input); Format probeFormat_OCCBREP(const System::FormatProbeInput& input); Format probeFormat_STL(const System::FormatProbeInput& input); Format probeFormat_OBJ(const System::FormatProbeInput& input); +Format probeFormat_PLY(const System::FormatProbeInput& input); void addPredefinedFormatProbes(System* system); } // namespace IO diff --git a/src/base/io_writer.cpp b/src/base/io_writer.cpp index 2e75b7ed..c51bfeca 100644 --- a/src/base/io_writer.cpp +++ b/src/base/io_writer.cpp @@ -5,23 +5,9 @@ ****************************************************************************/ #include "io_writer.h" -#include "messenger.h" namespace Mayo { namespace IO { -Writer::Writer() - : m_messenger(NullMessenger::instance()) -{ -} - -void Writer::setMessenger(Messenger* messenger) -{ - if (messenger) - m_messenger = messenger; - else - m_messenger = NullMessenger::instance(); -} - } // namespace IO } // namespace Mayo diff --git a/src/base/io_writer.h b/src/base/io_writer.h index 7b522850..f9f269b5 100644 --- a/src/base/io_writer.h +++ b/src/base/io_writer.h @@ -8,39 +8,51 @@ #include "filepath.h" #include "io_format.h" +#include "messenger_client.h" #include "span.h" #include namespace Mayo { class ApplicationItem; -class Messenger; class PropertyGroup; class TaskProgress; namespace IO { -class Writer { +// Abstract base class for writers +// Provides services for writing files in two steps: +// - transfer a list of items to be written +// - write transferred items into target file +class Writer : public MessengerClient { public: - Writer(); virtual ~Writer() = default; + // Converts items(documents and document nodes) into data ready to be written + // Returns 'true' on success virtual bool transfer(Span appItems, TaskProgress* progress) = 0; - virtual bool writeFile(const FilePath& fp, TaskProgress* progress) = 0; - virtual void applyProperties(const PropertyGroup* /*params*/) {} - Messenger* messenger() const { return m_messenger; } - void setMessenger(Messenger* messenger); + // Writes contents(items passed to transfer()) to the file at path 'fp' + // Returns 'true' on success + virtual bool writeFile(const FilePath& fp, TaskProgress* progress) = 0; -private: - Messenger* m_messenger = nullptr; + // Apply properties contain in 'group' to the writer's parameter values(known in writer sub-class) + virtual void applyProperties(const PropertyGroup* group) = 0; }; +// Abstract base class for all writer factories class FactoryWriter { public: virtual ~FactoryWriter() = default; + + // Returns supported formats, ie the formats this factory can create writers for virtual Span formats() const = 0; + + // Creates and returns a Writer object that matches the given format, or nullptr if no matching writer is found virtual std::unique_ptr create(Format format) const = 0; + + // Creates and returns properties that match the given format. Those properties is a generic + // way to change parameter values of a Writer object corresponding to format(see also Writer::applyProperties()) virtual std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const = 0; }; diff --git a/src/base/libtree.h b/src/base/libtree.h index 1cf81946..7d4bd0bd 100644 --- a/src/base/libtree.h +++ b/src/base/libtree.h @@ -12,26 +12,65 @@ namespace Mayo { +// Tree node identifier type using TreeNodeId = uint32_t; +// Provides tree-like organization of data +// +// A tree node satisfies the following properties: +// * accessible through an identifier which is unique in the tree it belongs to +// * null identifier(0) refers to null(void) tree node +// * single parent node. A tree node is a root in case its parent identifier is null +// * has 0..N child tree nodes. A tree node is a leaf in case it has no child +// * owns data of any type, though data type is the same for all nodes of the same tree +// +// Storage of nodes and associated data is memory efficient : all nodes are stored in a single array +// Use the traverseTree_() family of functions to visit nodes of a Tree object +// +// Data type 'T' must be default-constructible(see https://www.cplusplus.com/reference/type_traits/is_default_constructible/) +// template class Tree { public: Tree(); + // Identifier of the sibling node previous to 'id'. Might returns 0 TreeNodeId nodeSiblingPrevious(TreeNodeId id) const; + + // Identifier of the sibling node next to 'id'. Might returns 0 TreeNodeId nodeSiblingNext(TreeNodeId id) const; + + // Identifier of the first child of node 'id'. Might returns 0 TreeNodeId nodeChildFirst(TreeNodeId id) const; + + // Identifier of the last child of node 'id'. Might returns 0 TreeNodeId nodeChildLast(TreeNodeId id) const; + + // Identifier of the parent of node 'id'. Might returns 0(in such case 'id' is a root) TreeNodeId nodeParent(TreeNodeId id) const; + + // Identifier of the node being root of node 'id' TreeNodeId nodeRoot(TreeNodeId id) const; + + // Data associated to node of identifier 'id' or default-constructed object of type 'T' in case of error const T& nodeData(TreeNodeId id) const; + + // Is node of identifier 'id' a root? Note: a root as parent nodes(ie nodeParent(id) == 0) bool nodeIsRoot(TreeNodeId id) const; + + // Is node of identifier 'id' a leaf? Note: a leaf as no child nodes bool nodeIsLeaf(TreeNodeId id) const; + + // Read-only array of all the roots Span roots() const; + // Removes all nodes, tree will become empty void clear(); + + // Appends child to node identified by 'parentId'. That new node will contain 'data' TreeNodeId appendChild(TreeNodeId parentId, const T& data); TreeNodeId appendChild(TreeNodeId parentId, T&& data); + + // Remove root node identified by 'id' void removeRoot(TreeNodeId id); private: @@ -67,6 +106,10 @@ template class Tree { std::vector m_vecRoot; }; +enum class TreeTraversal { + Unorder, PreOrder, PostOrder +}; + // Fastest tree traversal, but nodes are visited unordered template void traverseTree_unorder(const Tree& tree, const FN& callback); @@ -83,13 +126,11 @@ void traverseTree_postOrder(const Tree& tree, const FN& callback); template void traverseTree_postOrder(TreeNodeId id, const Tree& tree, const FN& callback); -// Same as traverseTree_preOrder() template -void traverseTree(const Tree& tree, const FN& callback); +void traverseTree(const Tree& tree, const FN& callback, TreeTraversal mode = TreeTraversal::PreOrder); -// Same as traverseTree_preOrder() template -void traverseTree(TreeNodeId id, const Tree& tree, const FN& callback); +void traverseTree(TreeNodeId id, const Tree& tree, const FN& callback, TreeTraversal mode = TreeTraversal::PreOrder); template void visitDirectChildren(TreeNodeId id, const Tree& tree, const FN& callback); @@ -204,7 +245,7 @@ template void Tree::removeRoot(TreeNodeId id) { Expects(this->nodeIsRoot(id)); - // TODO Mark all children nodes as 'deleted' + // TODO Mark all deep children nodes as 'deleted' auto it = std::find(m_vecRoot.begin(), m_vecRoot.end(), id); if (it != m_vecRoot.end()) { TreeNode* node = this->ptrNode(id); @@ -233,23 +274,36 @@ const typename Tree::TreeNode* Tree::ptrNode(TreeNodeId id) const { } template -void traverseTree_unorder(const Tree& tree, const FN& callback) -{ - for (const typename Tree::TreeNode& node : tree.m_vecNode) { - const TreeNodeId id = (&node - &tree.m_vecNode.front()) + 1; - if (!tree.isNodeDeleted(id)) - callback(id); +void traverseTree(const Tree& tree, const FN& callback, TreeTraversal mode) { + switch (mode) { + case TreeTraversal::Unorder: + return traverseTree_unorder(tree, callback); + case TreeTraversal::PreOrder: + return traverseTree_preOrder(tree, callback); + case TreeTraversal::PostOrder: + return traverseTree_postOrder(tree, callback); } } template -void traverseTree(const Tree& tree, const FN& callback) { - return traverseTree_preOrder(tree, callback); +void traverseTree(TreeNodeId id, const Tree& tree, const FN& callback, TreeTraversal mode) { + switch (mode) { + case TreeTraversal::Unorder: + case TreeTraversal::PreOrder: + return traverseTree_preOrder(id, tree, callback); + case TreeTraversal::PostOrder: + return traverseTree_postOrder(id, tree, callback); + } } template -void traverseTree(TreeNodeId id, const Tree& tree, const FN& callback) { - return traverseTree_preOrder(id, tree, callback); +void traverseTree_unorder(const Tree& tree, const FN& callback) +{ + for (const typename Tree::TreeNode& node : tree.m_vecNode) { + const TreeNodeId id = (&node - &tree.m_vecNode.front()) + 1; + if (!tree.isNodeDeleted(id)) + callback(id); + } } template diff --git a/src/base/math_utils.cpp b/src/base/math_utils.cpp index 5e31d26c..b71a3d7f 100644 --- a/src/base/math_utils.cpp +++ b/src/base/math_utils.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace Mayo { @@ -43,8 +44,8 @@ std::pair planeRange(const BndBoxCoords& bbc, const gp_Dir& plan const gp_Vec n(isReversedStandardDir(planeNormal) ? planeNormal.Reversed() : planeNormal); bool isMaxValid = false; bool isMinValid = false; - double rmax; - double rmin; + double rmax = std::numeric_limits::lowest(); + double rmin = std::numeric_limits::max(); for (const gp_Pnt& bndPoint : bbc.vertices()) { const gp_Vec vec(bndPoint.XYZ()); const double dot = n.Dot(vec); diff --git a/src/base/math_utils.h b/src/base/math_utils.h index 657671dc..bc3a23d3 100644 --- a/src/base/math_utils.h +++ b/src/base/math_utils.h @@ -16,22 +16,41 @@ namespace Mayo { struct BndBoxCoords; +// Provides helper functions for mathematics purpose namespace MathUtils { +// Returns the value 'val' which is in range [omin..omax] to the corresponding value in range [nmin..nmax] double mappedValue(double val, double omin, double omax, double nmin, double nmax); +// TODO: add toPercent() function + +// Is 'n' a standard direction being reversed(ie -X, -Y or -Z) ? bool isReversedStandardDir(const gp_Dir& n); + +// Returns linear position of a plane along its normal direction double planePosition(const gp_Pln& plane); + +// Returns the min/max range of linear positions along direction 'planeNormal' within box 'bbc' std::pair planeRange(const BndBoxCoords& bbc, const gp_Dir& planeNormal); +// Returns a + t(b − a) template static T lerp(T a, T b, U t); +// Returns true if the absolute value of 'f' is within 0.00001f of 0.0 inline bool fuzzyIsNull(float f) { return std::abs(f) <= 0.00001f; } + +// Returns true if the absolute value of 'd' is within 0.000000000001 of 0.0 inline bool fuzzyIsNull(double d) { return std::abs(d) <= 0.000000000001; } +// Compares the floating point value 'f1' and 'f2' and returns true if they are considered equal, otherwise false. +// Note that comparing values where either 'f1' or 'f2' is 0.0 will not work, nor does comparing values where one of +// the values is NaN or infinity. If one of the values is always 0.0, use fuzzyIsNull() instead. +// If one of the values is likely to be 0.0, one solution is to add 1.0 to both values inline bool fuzzyEqual(float f1, float f2) { return (std::abs(f1 - f2) * 100000.f <= std::min(std::abs(f1), std::abs(f2))); } + +// Same as fuzzyEqual(float, float) but for double-precision values inline bool fuzzyEqual(double d1, double d2) { return (std::abs(d1 - d2) * 1000000000000. <= std::min(std::abs(d1), std::abs(d2))); } @@ -44,8 +63,11 @@ inline bool fuzzyEqual(double d1, double d2) { template T lerp(T a, T b, U t) { - // TODO Call std::lerp() when available(C++20) +#ifdef __cpp_lib_interpolate + return std::lerp(a, b, t); +#else return T(a * (1 - t) + b * t); +#endif } } // namespace MathUtils diff --git a/src/base/mesh_access.cpp b/src/base/mesh_access.cpp new file mode 100644 index 00000000..a2f85827 --- /dev/null +++ b/src/base/mesh_access.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "mesh_access.h" + +#include "brep_utils.h" +#include "caf_utils.h" +#include "cpp_utils.h" +#include "data_triangulation.h" +#include "document.h" +#include "document_tree_node.h" + +#include +#include +#include +#if OCC_VERSION_HEX >= 0x070500 +# include +#endif + +#include + +namespace Mayo { + +// Provides mesh access to a TopoDS_Face object stored within XCAF document +class XCafFace_MeshAccess : public IMeshAccess { +public: + XCafFace_MeshAccess(const DocumentTreeNode& treeNode, const TopoDS_Face& face) + { + const DocumentPtr& doc = treeNode.document(); + const TDF_Label labelNode = treeNode.label(); + if (doc->xcaf().isShapeSubOf(labelNode, face)) + m_faceColor = findShapeColor(doc, doc->xcaf().findShapeLabel(face)); + + if (!m_faceColor) + m_faceColor = findShapeColor(doc, labelNode); + + const TopLoc_Location locShape = XCaf::shapeAbsoluteLocation(doc->modelTree(), treeNode.id()); + TopLoc_Location locFace; + m_triangulation = BRep_Tool::Triangulation(face, locFace); + m_location = locShape * locFace; + } + + std::optional nodeColor(int /*i*/) const override { + return m_faceColor; + } + + const TopLoc_Location& location() const override { + return m_location; + } + + const Handle(Poly_Triangulation)& triangulation() const override { + return m_triangulation; + } + +private: + static std::optional findShapeColor(const DocumentPtr& doc, const TDF_Label& labelShape) + { + if (doc->xcaf().hasShapeColor(labelShape)) + return doc->xcaf().shapeColor(labelShape); + +#if OCC_VERSION_HEX >= 0x070500 + Handle_XCAFDoc_VisMaterialTool visMaterialTool = doc->xcaf().visMaterialTool(); + Handle_XCAFDoc_VisMaterial visMaterial = visMaterialTool->GetShapeMaterial(labelShape); + if (visMaterial) + return visMaterial->BaseColor().GetRGB(); +#endif + + return {}; + } + + std::optional m_faceColor; + TopLoc_Location m_location; + Handle(Poly_Triangulation) m_triangulation; +}; + +// Provides access to a mesh stored within a DataTriangulation attribute +class DataTriangulation_MeshAccess : public IMeshAccess { +public: + DataTriangulation_MeshAccess(const DocumentTreeNode& treeNode) + : m_dataTriangulation(CafUtils::findAttribute(treeNode.label())) + { + } + + std::optional nodeColor(int i) const override + { + if (0 <= i && CppUtils::cmpLess(i, m_dataTriangulation->nodeColors().size())) + return m_dataTriangulation->nodeColors()[i]; + else + return {}; + } + + const TopLoc_Location& location() const override { + return m_location; + } + + const Handle(Poly_Triangulation)& triangulation() const override { + return m_dataTriangulation->Get(); + } + +private: + opencascade::handle m_dataTriangulation; + TopLoc_Location m_location; +}; + +void IMeshAccess_visitMeshes( + const DocumentTreeNode& treeNode, + std::function fnCallback) +{ + if (!fnCallback || !treeNode.isValid()) + return; + + auto fnProxyCallback = [&](const IMeshAccess& mesh) { + if (mesh.triangulation()) + fnCallback(mesh); + }; + if (XCaf::isShape(treeNode.label())) { + BRepUtils::forEachSubFace(XCaf::shape(treeNode.label()), [&](const TopoDS_Face& face) { + fnProxyCallback(XCafFace_MeshAccess(treeNode, face)); + }); + } + else if (CafUtils::hasAttribute(treeNode.label())) { + fnProxyCallback(DataTriangulation_MeshAccess(treeNode)); + } +} + +} // namespace Mayo diff --git a/src/base/mesh_access.h b/src/base/mesh_access.h new file mode 100644 index 00000000..db2b4c87 --- /dev/null +++ b/src/base/mesh_access.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +// Base +class DocumentTreeNode; + +// OpenCascade +#include +#include +class Poly_Triangulation; +class TopLoc_Location; + +// CppStd +#include +#include + +namespace Mayo { + +// Provides an interface to access mesh geometry +class IMeshAccess { +public: + virtual std::optional nodeColor(int i) const = 0; + virtual const TopLoc_Location& location() const = 0; + virtual const Handle(Poly_Triangulation)& triangulation() const = 0; +}; + +// Iterates over meshes from `treeNode` and call `fnCallback` for each item. +void IMeshAccess_visitMeshes( + const DocumentTreeNode& treeNode, + std::function fnCallback +); + +} // namespace Mayo diff --git a/src/base/mesh_utils.cpp b/src/base/mesh_utils.cpp index c3fa303e..827f9a6a 100644 --- a/src/base/mesh_utils.cpp +++ b/src/base/mesh_utils.cpp @@ -77,6 +77,37 @@ 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) +{ +#if OCC_VERSION_HEX >= 0x070600 + triangulation->SetTriangle(index, triangle); +#else + triangulation->ChangeTriangle(index) = triangle; +#endif +} + +void MeshUtils::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(); +#endif +} + +void MeshUtils::allocateNormals(const Handle_Poly_Triangulation& triangulation) +{ +#if OCC_VERSION_HEX >= 0x070600 + triangulation->AddNormals(); +#else + auto normalCoords = new TShort_HArray1OfShortReal(1, 3 * triangulation->NbNodes()); + triangulation->SetNormals(normalCoords); +#endif +} + // Adapted from http://cs.smith.edu/~jorourke/Code/polyorient.C MeshUtils::Orientation MeshUtils::orientation(const AdaptorPolyline2d& polyline) { diff --git a/src/base/mesh_utils.h b/src/base/mesh_utils.h index 5e174e48..93055722 100644 --- a/src/base/mesh_utils.h +++ b/src/base/mesh_utils.h @@ -7,6 +7,7 @@ #pragma once #include +#include class gp_XYZ; namespace Mayo { @@ -18,7 +19,16 @@ struct MeshUtils { static double triangulationVolume(const Handle_Poly_Triangulation& triangulation); static double triangulationArea(const Handle_Poly_Triangulation& triangulation); +#if OCC_VERSION_HEX >= 0x070600 + using Poly_Triangulation_NormalType = gp_Vec3f; +#else + 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); enum class Orientation { Unknown, diff --git a/src/base/messenger.cpp b/src/base/messenger.cpp index a1cb5564..fa299e03 100644 --- a/src/base/messenger.cpp +++ b/src/base/messenger.cpp @@ -8,6 +8,15 @@ namespace Mayo { +namespace { + +class NullMessenger : public Messenger { +public: + void emitMessage(MessageType, std::string_view) override {} +}; + +} // namespace + void Messenger::emitTrace(std::string_view text) { this->emitMessage(MessageType::Trace, text); @@ -28,6 +37,12 @@ void Messenger::emitError(std::string_view text) this->emitMessage(MessageType::Error, text); } +Messenger& Messenger::null() +{ + static NullMessenger null; + return null; +} + MessengerByCallback::MessengerByCallback(std::function fnCallback) : m_fnCallback(std::move(fnCallback)) { @@ -39,14 +54,4 @@ void MessengerByCallback::emitMessage(Messenger::MessageType msgType, std::strin m_fnCallback(msgType, text); } -void NullMessenger::emitMessage(Messenger::MessageType /*msgType*/, std::string_view /*text*/) -{ -} - -Messenger* NullMessenger::instance() -{ - static NullMessenger nullMsg; - return &nullMsg; -} - } // namespace Mayo diff --git a/src/base/messenger.h b/src/base/messenger.h index 963a6e26..d221021d 100644 --- a/src/base/messenger.h +++ b/src/base/messenger.h @@ -25,6 +25,8 @@ class Messenger { void emitWarning(std::string_view text); void emitError(std::string_view text); virtual void emitMessage(MessageType msgType, std::string_view text) = 0; + + static Messenger& null(); }; // Provides facility to construct a Messenger object from a lambda @@ -38,13 +40,4 @@ class MessengerByCallback : public Messenger { std::function m_fnCallback; }; -class NullMessenger : public Messenger { -public: - static Messenger* instance(); - void emitMessage(MessageType msgType, std::string_view text) override; - -private: - NullMessenger() = default; -}; - } // namespace Mayo diff --git a/src/base/messenger_client.cpp b/src/base/messenger_client.cpp new file mode 100644 index 00000000..0e1a27f8 --- /dev/null +++ b/src/base/messenger_client.cpp @@ -0,0 +1,25 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "messenger_client.h" +#include "messenger.h" + +namespace Mayo { + +MessengerClient::MessengerClient() + : m_messenger(&Messenger::null()) +{ +} + +void MessengerClient::setMessenger(Messenger* messenger) +{ + if (messenger) + m_messenger = messenger; + else + m_messenger = &Messenger::null(); +} + +} // namespace Mayo diff --git a/src/base/messenger_client.h b/src/base/messenger_client.h new file mode 100644 index 00000000..26d48769 --- /dev/null +++ b/src/base/messenger_client.h @@ -0,0 +1,26 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +namespace Mayo { + +class Messenger; + +// Provides a link to a messenger object +// The object returned by MessengerClient::messenger() is garanteed to be non-nullptr +class MessengerClient { +public: + MessengerClient(); + + Messenger* messenger() const { return m_messenger; } + void setMessenger(Messenger* messenger); + +private: + Messenger* m_messenger = nullptr; +}; + +} // namespace Mayo diff --git a/src/base/property.h b/src/base/property.h index 6051c03e..1f146c63 100644 --- a/src/base/property.h +++ b/src/base/property.h @@ -19,6 +19,12 @@ namespace Mayo { class Property; // Provides a cohesive container of Property objects +// PropertyGroup defines callbacks to be executed when special events happen on contained properties: +// - a property value is about to be changed +// - a property value was changed +// - the "enabled" status of a property was toggled +// A PropertyGroup can be linked to a parent group(optional). In such case the child group executes +// as well the parent group's callbacks. class PropertyGroup { public: PropertyGroup(PropertyGroup* parentGroup = nullptr); diff --git a/src/base/property_enumeration.cpp b/src/base/property_enumeration.cpp index 091b5d01..27131804 100644 --- a/src/base/property_enumeration.cpp +++ b/src/base/property_enumeration.cpp @@ -6,13 +6,34 @@ #include "property_enumeration.h" +#include + namespace Mayo { PropertyEnumeration::PropertyEnumeration( - PropertyGroup* grp, const TextId& name, const Enumeration& enumeration) + PropertyGroup* grp, const TextId& name, const Enumeration* enumeration) : Property(grp, name), - m_enumeration(enumeration) + m_enumeration(enumeration), + m_value(enumeration && enumeration->empty() ? enumeration->itemAt(0).value : -1) +{ +} + +const Enumeration& PropertyEnumeration::enumeration() const +{ + if (!m_enumeration) + throw std::runtime_error("Internal Enumeration object must not be null"); + + return *m_enumeration; +} + +PropertyEnumeration::PropertyEnumeration(PropertyGroup* grp, const TextId& name) + : Property(grp, name) +{ +} + +void PropertyEnumeration::setEnumeration(const Enumeration* enumeration) { + m_enumeration = enumeration; } void PropertyEnumeration::addDescription(Enumeration::Value value, std::string_view descr) @@ -36,7 +57,7 @@ void PropertyEnumeration::clearDescriptions() std::string_view PropertyEnumeration::name() const { - return m_enumeration.findName(m_value); + return m_enumeration ? m_enumeration->findNameByValue(m_value) : std::string_view{}; } Enumeration::Value PropertyEnumeration::value() const @@ -52,7 +73,10 @@ bool PropertyEnumeration::setValue(Enumeration::Value value) bool PropertyEnumeration::setValueByName(std::string_view name) { - return Property::setValueHelper(this, &m_value, m_enumeration.findValue(name)); + if (!m_enumeration) + return false; + + return Property::setValueHelper(this, &m_value, m_enumeration->findValueByName(name)); } const char* PropertyEnumeration::dynTypeName() const diff --git a/src/base/property_enumeration.h b/src/base/property_enumeration.h index 65a1a38b..e234a0fa 100644 --- a/src/base/property_enumeration.h +++ b/src/base/property_enumeration.h @@ -24,9 +24,10 @@ namespace Mayo { class PropertyEnumeration : public Property { public: - PropertyEnumeration(PropertyGroup* grp, const TextId& name, const Enumeration& enumeration); + // This constructor should be used when 'enumeration' is garanteed to be fully constructed + PropertyEnumeration(PropertyGroup* grp, const TextId& name, const Enumeration* enumeration); - constexpr const Enumeration& enumeration() const { return m_enumeration; } + const Enumeration& enumeration() const; bool descriptionsEmpty() const { return m_vecDescription.empty(); } void addDescription(Enumeration::Value value, std::string_view descr); @@ -41,14 +42,21 @@ class PropertyEnumeration : public Property { const char* dynTypeName() const override; static const char TypeName[]; +protected: + // This constructor should be used when 'enumeration' isn't completely constructed, as in + // PropertyEnum<> subclass. The enumeration member is set with the setEnumeration() helper + PropertyEnumeration(PropertyGroup* grp, const TextId& name); + + void setEnumeration(const Enumeration* enumeration); + private: struct Description { Enumeration::Value value; std::string text; }; - Enumeration::Value m_value; - const Enumeration& m_enumeration; + const Enumeration* m_enumeration = nullptr; + Enumeration::Value m_value = -1; std::vector m_vecDescription; }; @@ -79,9 +87,11 @@ class PropertyEnum : public PropertyEnumeration { template PropertyEnum::PropertyEnum(PropertyGroup* grp, const TextId& name) - : m_enum(Enumeration::fromType()), - PropertyEnumeration(grp, name, m_enum) -{ } + : PropertyEnumeration(grp, name), + m_enum(Enumeration::fromType()) +{ + this->setEnumeration(&m_enum); +} template ENUM PropertyEnum::value() const { diff --git a/src/base/property_value_conversion.cpp b/src/base/property_value_conversion.cpp index e81bc9a5..de223236 100644 --- a/src/base/property_value_conversion.cpp +++ b/src/base/property_value_conversion.cpp @@ -248,7 +248,7 @@ bool PropertyValueConversion::fromVariant(Property* prop, const Variant& variant else if (isType(prop)) { if (variant.isConvertibleToConstRefString()) { const std::string& name = variant.toConstRefString(); - const Enumeration::Item* ptrItem = ptr(prop)->enumeration().findItem(name); + const Enumeration::Item* ptrItem = ptr(prop)->enumeration().findItemByName(name); if (ptrItem) return ptr(prop)->setValue(ptrItem->value); diff --git a/src/base/quantity.h b/src/base/quantity.h index 94d3cd89..ca5fcc01 100644 --- a/src/base/quantity.h +++ b/src/base/quantity.h @@ -71,22 +71,36 @@ constexpr QuantityLength Quantity_Millimeter(1.); constexpr QuantityLength Quantity_Centimeter(10.); constexpr QuantityLength Quantity_Decimeter(100.); constexpr QuantityLength Quantity_Meter(1000.); -constexpr QuantityLength Quantity_Kilometre(1e6); +constexpr QuantityLength Quantity_Kilometer(1e6); +constexpr QuantityLength Quantity_NauticalMile(1852 * Quantity_Kilometer.value()); -constexpr QuantityArea Quantity_SquaredMillimeter(1.); -constexpr QuantityArea Quantity_SquaredCentimer(100.); -constexpr QuantityArea Quantity_SquaredMeter(1e6); -constexpr QuantityArea Quantity_SquaredKilometer(1e12); +constexpr QuantityArea Quantity_SquareMillimeter(1.); +constexpr QuantityArea Quantity_SquareCentimeter(100.); +constexpr QuantityArea Quantity_SquareMeter(1e6); +constexpr QuantityArea Quantity_SquareKilometer(1e12); constexpr QuantityVolume Quantity_CubicMillimeter(1.); -constexpr QuantityVolume Quantity_CubicCentimer(1000.); +constexpr QuantityVolume Quantity_CubicCentimeter(1000.); constexpr QuantityVolume Quantity_CubicMeter(1e9); +constexpr QuantityLength Quantity_Thou(0.0254); constexpr QuantityLength Quantity_Inch(25.4); +constexpr QuantityLength Quantity_Link(201.168); constexpr QuantityLength Quantity_Foot(304.8); -constexpr QuantityLength Quantity_Thou(0.0254); constexpr QuantityLength Quantity_Yard(914.4); -constexpr QuantityLength Quantity_Mile(1609344.); +constexpr QuantityLength Quantity_Rod(5029.2); +constexpr QuantityLength Quantity_Chain(20116.8); +constexpr QuantityLength Quantity_Furlong(201168); +constexpr QuantityLength Quantity_Mile(1609344); +constexpr QuantityLength Quantity_League(4828032); + +constexpr QuantityArea Quantity_SquareInch(654.16); +constexpr QuantityArea Quantity_SquareFoot(92903.04); +constexpr QuantityArea Quantity_SquareYard(836127.36); +constexpr QuantityArea Quantity_SquareMile(Quantity_Mile.value() * Quantity_Mile.value()); + +constexpr QuantityVolume Quantity_CubicInch(16387.064); +constexpr QuantityVolume Quantity_CubicFoot(Quantity_Foot.value() * Quantity_SquareFoot.value()); constexpr QuantityVelocity Quantity_MillimeterPerSecond(1.); diff --git a/src/base/settings.cpp b/src/base/settings.cpp index b3d0e521..ed8099c3 100644 --- a/src/base/settings.cpp +++ b/src/base/settings.cpp @@ -193,9 +193,9 @@ const PropertyValueConversion& Settings::propertyValueConversion() const return *(d->m_propValueConverter); } -void Settings::setPropertyValueConversion(const PropertyValueConversion& conv) +void Settings::setPropertyValueConversion(const PropertyValueConversion* conv) { - d->m_propValueConverter = &conv; + d->m_propValueConverter = conv; } int Settings::groupCount() const diff --git a/src/base/settings.h b/src/base/settings.h index 0e0941b9..102f905a 100644 --- a/src/base/settings.h +++ b/src/base/settings.h @@ -24,6 +24,7 @@ class Settings : public QObject, public PropertyGroup { class Storage { public: + virtual ~Storage() = default; virtual bool contains(std::string_view key) const = 0; virtual Variant value(std::string_view key) const = 0; virtual void setValue(std::string_view key, const Variant& value) = 0; @@ -37,6 +38,8 @@ class Settings : public QObject, public PropertyGroup { using ResetFunction = std::function; Settings(QObject* parent = nullptr); + Settings(const Settings&) = delete; // Not copyable + Settings& operator=(const Settings&) = delete; // Not copyable ~Settings(); void setStorage(std::unique_ptr ptrStorage); @@ -51,7 +54,7 @@ class Settings : public QObject, public PropertyGroup { void saveAs(Storage* target, const ExcludePropertyPredicate& fnExclude = nullptr); const PropertyValueConversion& propertyValueConversion() const; - void setPropertyValueConversion(const PropertyValueConversion& conv); + void setPropertyValueConversion(const PropertyValueConversion* conv); int groupCount() const; std::string_view groupIdentifier(GroupIndex index) const; diff --git a/src/base/string_conv.h b/src/base/string_conv.h index 8c87c448..a335a9c4 100644 --- a/src/base/string_conv.h +++ b/src/base/string_conv.h @@ -42,6 +42,12 @@ std::string_view to_stdStringView(const STRING_TYPE& str) { return string_conv(str); } +// X -> TCollection_AsciiString +template +TCollection_AsciiString to_OccAsciiString(const STRING_TYPE& str) { + return string_conv(str); +} + // X -> TCollection_ExtendedString template TCollection_ExtendedString to_OccExtString(const STRING_TYPE& str) { @@ -77,6 +83,17 @@ template<> struct StringConv { }; #endif +// -- +// -- std::string_view -> X +// -- + +// std::string_view -> TCollection_AsciiString +template<> struct StringConv { + static auto to(std::string_view str) { + return TCollection_AsciiString(str.data(), int(str.size())); + } +}; + // -- // -- Handle(TCollection_HAsciiString) -> X // -- diff --git a/src/base/task_manager.cpp b/src/base/task_manager.cpp index 268b4087..51b0fb46 100644 --- a/src/base/task_manager.cpp +++ b/src/base/task_manager.cpp @@ -34,15 +34,6 @@ TaskManager::~TaskManager() it = m_mapEntity.erase(it); } -TaskManager* TaskManager::globalInstance() -{ - static TaskManager* global = nullptr; - if (!global) - global = new TaskManager(Application::instance().get()); - - return global; -} - TaskId TaskManager::newTask(TaskJob fn) { const TaskId taskId = m_taskIdSeq.fetch_add(1); @@ -120,7 +111,7 @@ int TaskManager::globalProgress() const taskAccumPct += ptrEntity->taskProgress.value(); } - const int taskCount = m_mapEntity.size(); + const int taskCount = CppUtils::safeStaticCast(m_mapEntity.size()); const int newGlobalPct = MathUtils::mappedValue(taskAccumPct, 0, taskCount * 100, 0, 100); //qDebug() << "taskCount=" << taskCount << " taskAccumPct=" << taskAccumPct << " newGlobalPct=" << newGlobalPct; return newGlobalPct; diff --git a/src/base/task_manager.h b/src/base/task_manager.h index caff67d1..b912da9a 100644 --- a/src/base/task_manager.h +++ b/src/base/task_manager.h @@ -24,7 +24,6 @@ class TaskManager : public QObject { public: TaskManager(QObject* parent = nullptr); ~TaskManager(); - static TaskManager* globalInstance(); TaskId newTask(TaskJob fn); void run(TaskId id, TaskAutoDestroy policy = TaskAutoDestroy::On); diff --git a/src/base/task_progress.cpp b/src/base/task_progress.cpp index ea9f70c2..babbe7b3 100644 --- a/src/base/task_progress.cpp +++ b/src/base/task_progress.cpp @@ -33,6 +33,17 @@ TaskProgress::~TaskProgress() this->setValue(100); } +TaskProgress& TaskProgress::null() +{ + static TaskProgress null; + return null; +} + +bool TaskProgress::isNull() const +{ + return m_task == nullptr; +} + TaskId TaskProgress::taskId() const { return m_task ? m_task->id() : std::numeric_limits::max(); @@ -45,6 +56,9 @@ TaskManager* TaskProgress::taskManager() const void TaskProgress::setValue(int pct) { + if (this->isNull()) + return; + if (m_isAbortRequested) return; @@ -58,16 +72,16 @@ void TaskProgress::setValue(int pct) m_parent->setValue(m_parent->value() + valueDeltaInParent); } else { - if (m_task) - emit m_task->manager()->progressChanged(m_task->id(), m_value); + emit m_task->manager()->progressChanged(m_task->id(), m_value); } } void TaskProgress::setStep(std::string_view title) { - m_step = title; - if (m_task) + if (!this->isNull()) { + m_step = title; emit m_task->manager()->progressStep(m_task->id(), m_step); + } } void TaskProgress::setTask(const Task* task) @@ -82,7 +96,8 @@ bool TaskProgress::isAbortRequested(const TaskProgress* progress) void TaskProgress::requestAbort() { - m_isAbortRequested = true; + if (!this->isNull()) + m_isAbortRequested = true; } } // namespace Mayo diff --git a/src/base/task_progress.h b/src/base/task_progress.h index f7c589ad..7d7d458d 100644 --- a/src/base/task_progress.h +++ b/src/base/task_progress.h @@ -22,6 +22,9 @@ class TaskProgress { TaskProgress(TaskProgress* parent, double portionSize, std::string_view step = {}); ~TaskProgress(); + static TaskProgress& null(); + bool isNull() const; + TaskId taskId() const; TaskManager* taskManager() const; diff --git a/src/base/tkernel_utils.cpp b/src/base/tkernel_utils.cpp index 00a13c40..c9a97875 100644 --- a/src/base/tkernel_utils.cpp +++ b/src/base/tkernel_utils.cpp @@ -107,4 +107,14 @@ bool TKernelUtils::colorFromHex(std::string_view strHex, Quantity_Color* color) return true; } +Quantity_TypeOfColor TKernelUtils::preferredRgbColorType() +{ + // See https://dev.opencascade.org/content/occt-3d-viewer-becomes-srgb-aware +#if OCC_VERSION_HEX >= 0x070500 + return Quantity_TOC_sRGB; +#else + return Quantity_TOC_RGB; +#endif +} + } // namespace Mayo diff --git a/src/base/tkernel_utils.h b/src/base/tkernel_utils.h index bd8421a6..72efb7a9 100644 --- a/src/base/tkernel_utils.h +++ b/src/base/tkernel_utils.h @@ -44,6 +44,9 @@ class TKernelUtils { // Decodes a string containing a color with #RRGGBB format to a Quantity_Color object // RR, GG, BB are in hexadecimal notation static bool colorFromHex(std::string_view strHex, Quantity_Color* color); + + // Returns the type to be used(by default) for RGB colors, depending on OpenCascasde version + static Quantity_TypeOfColor preferredRgbColorType(); }; } // namespace Mayo diff --git a/src/base/unit.h b/src/base/unit.h index 3fcb3c5c..16a0199f 100644 --- a/src/base/unit.h +++ b/src/base/unit.h @@ -30,6 +30,25 @@ enum class Unit { Pressure // kg/m.s² (or N/m²) }; +enum LengthUnit { + // SI + Nanometer, Micrometer, Millimeter, Centimeter, Decimeter, Meter, Kilometer, + NauticalMile, + // Imperial UK + Thou, Inch, Link, Foot, Yard, Rod, Chain, Furlong, Mile, League +}; + +enum AngleUnit { + Radian, Degree +}; + +enum AreaUnit { + // SI + SquareMillimeter, SquareCentimeter, SquareMeter, SquareKilometer, + // Imperial UK + SquareInch, SquareFoot, SquareYard, SquareMile +}; + #if 0 enum class BaseUnit { Length = 0, // Meter(m) diff --git a/src/base/unit_system.cpp b/src/base/unit_system.cpp index 99bdba0b..3e15f6e0 100644 --- a/src/base/unit_system.cpp +++ b/src/base/unit_system.cpp @@ -45,27 +45,29 @@ struct UnitInfo { }; const UnitInfo arrayUnitInfo_SI[] = { //Length - { Unit::Length, "mm", 1. }, - { Unit::Length, "m", 1000. }, - { Unit::Length, "nm", 1e-6 }, - { Unit::Length, "µm", 0.001 }, - { Unit::Length, "mm", 1. }, - { Unit::Length, "m", 1000. }, - { Unit::Length, "km", 1e6 }, + { Unit::Length, "mm", Quantity_Millimeter.value() }, + { Unit::Length, "cm", Quantity_Centimeter.value() }, + { Unit::Length, "dm", Quantity_Decimeter.value() }, + { Unit::Length, "m", Quantity_Meter.value() }, + { Unit::Length, "km", Quantity_Kilometer.value() }, + { Unit::Length, "nm", Quantity_Nanometer.value() }, + { Unit::Length, "µm", Quantity_Micrometer.value() }, // Angle - { Unit::Angle, "rad", 1. }, + { Unit::Angle, "rad", Quantity_Radian.value() }, { Unit::Angle, "deg", Quantity_Degree.value() }, - { Unit::Angle, "°", Quantity_Degree.value() }, + { Unit::Angle, "°", Quantity_Degree.value() }, // Area - { Unit::Area, "mm²", 1. }, - { Unit::Area, "m²", 1e6 }, - { Unit::Area, "km²", 1e12 }, + { Unit::Area, "mm²", Quantity_SquareMillimeter.value() }, + { Unit::Area, "cm²", Quantity_SquareCentimeter.value() }, + { Unit::Area, "m²", Quantity_SquareMeter.value() }, + { Unit::Area, "km²", Quantity_SquareKilometer.value() }, // Volume - { Unit::Volume, "mm³", 1. }, - { Unit::Volume, "m³", 1e9 }, - { Unit::Volume, "km³", 1e18 }, + { Unit::Volume, "mm³", Quantity_CubicMillimeter.value() }, + { Unit::Volume, "cm³", Quantity_CubicCentimeter.value() }, + { Unit::Volume, "m³", Quantity_CubicMeter.value() }, + { Unit::Volume, "L", Quantity_Liter.value() }, // Velocity - { Unit::Velocity, "mm/s", 1. }, + { Unit::Velocity, "mm/s", Quantity_MillimeterPerSecond.value() }, // Density { Unit::Density, "kg/m³", 1 }, { Unit::Density, "g/m³", 1000. }, @@ -81,16 +83,25 @@ const UnitInfo arrayUnitInfo_SI[] = { const UnitInfo arrayUnitInfo_ImperialUK[] = { //Length - { Unit::Length, "in", 25.4 }, - { Unit::Length, "thou", 0.0254 }, - { Unit::Length, "\"", 25.4 }, - { Unit::Length, "'", 304.8 }, - { Unit::Length, "yd", 914.4 }, - { Unit::Length, "mi", 1609344 }, + { Unit::Length, "thou", Quantity_Thou.value() }, + { Unit::Length, "in", Quantity_Inch.value() }, + { Unit::Length, "\"", Quantity_Inch.value() }, + { Unit::Length, "ft", Quantity_Foot.value() }, + { Unit::Length, "'", Quantity_Foot.value() }, + { Unit::Length, "yd", Quantity_Yard.value() }, + { Unit::Length, "mi", Quantity_Mile.value() }, // Others - { Unit::Area, "in²", 654.16 }, - { Unit::Volume, "in³", 16387.064 }, - { Unit::Velocity, "in/min", 25.4 / 60. } + { Unit::Area, "in²", Quantity_SquareInch.value() }, + { Unit::Area, "\"²", Quantity_SquareInch.value() }, + { Unit::Area, "ft²", Quantity_SquareFoot.value() }, + { Unit::Area, "'²", Quantity_SquareFoot.value() }, + { Unit::Area, "yd²", Quantity_SquareYard.value() }, + { Unit::Volume, "in³", Quantity_CubicInch.value() }, + { Unit::Volume, "\"³", Quantity_CubicInch.value() }, + { Unit::Volume, "ft³", Quantity_CubicFoot.value() }, + { Unit::Volume, "'³", Quantity_CubicFoot.value() }, + { Unit::Velocity, "in/min", Quantity_Inch.value() / 60. }, + { Unit::Velocity, "\"/min", Quantity_Inch.value() / 60. }, }; static UnitSystem::TranslateResult translateSI(double value, Unit unit) @@ -272,6 +283,65 @@ UnitSystem::TranslateResult UnitSystem::parseQuantity(std::string_view strQuanti return {}; } +UnitSystem::TranslateResult UnitSystem::translateLength(QuantityLength length, LengthUnit unit) +{ + auto fnTrResult = [=](QuantityLength qtyFactor, const char* strUnit) { + return TranslateResult{ (length / qtyFactor.value()).value(), strUnit, qtyFactor.value() }; + }; + switch (unit) { + // SI + case LengthUnit::Nanometer: return fnTrResult(Quantity_Nanometer, "nm"); + case LengthUnit::Micrometer: return fnTrResult(Quantity_Micrometer, "µm"); + case LengthUnit::Millimeter: return fnTrResult(Quantity_Millimeter, "mm"); + case LengthUnit::Centimeter: return fnTrResult(Quantity_Centimeter, "cm"); + case LengthUnit::Decimeter: return fnTrResult(Quantity_Decimeter, "dm"); + case LengthUnit::Meter: return fnTrResult(Quantity_Meter, "m"); + case LengthUnit::Kilometer: return fnTrResult(Quantity_Kilometer, "km"); + case LengthUnit::NauticalMile: return fnTrResult(Quantity_NauticalMile, "nmi"); + // Imperial UK + case LengthUnit::Thou: return fnTrResult(Quantity_Thou, "thou"); + case LengthUnit::Inch: return fnTrResult(Quantity_Inch, "in"); + case LengthUnit::Link: return fnTrResult(Quantity_Link, "link"); + case LengthUnit::Foot: return fnTrResult(Quantity_Foot, "ft"); + case LengthUnit::Yard: return fnTrResult(Quantity_Yard, "yd"); + case LengthUnit::Rod: return fnTrResult(Quantity_Rod, "rod"); + case LengthUnit::Chain: return fnTrResult(Quantity_Chain, "chain"); + case LengthUnit::Furlong: return fnTrResult(Quantity_Furlong, "fur"); + case LengthUnit::Mile: return fnTrResult(Quantity_Mile, "mi"); + case LengthUnit::League: return fnTrResult(Quantity_League, "league"); + } + return {}; +} + +UnitSystem::TranslateResult UnitSystem::translateArea(QuantityArea area, AreaUnit unit) +{ + auto fnTrResult = [=](QuantityArea qtyFactor, const char* strUnit) { + return TranslateResult{ (area / qtyFactor.value()).value(), strUnit, qtyFactor.value() }; + }; + switch (unit) { + // SI + case AreaUnit::SquareMillimeter: return fnTrResult(Quantity_SquareMillimeter, "mm²"); + case AreaUnit::SquareCentimeter: return fnTrResult(Quantity_SquareCentimeter, "cm²"); + case AreaUnit::SquareMeter: return fnTrResult(Quantity_SquareMeter, "m²"); + case AreaUnit::SquareKilometer: return fnTrResult(Quantity_SquareKilometer, "km²"); + // Imperial UK + case AreaUnit::SquareInch: return fnTrResult(Quantity_SquareInch, "in²"); + case AreaUnit::SquareFoot: return fnTrResult(Quantity_SquareFoot, "ft²"); + case AreaUnit::SquareYard: return fnTrResult(Quantity_SquareYard, "yd²"); + case AreaUnit::SquareMile: return fnTrResult(Quantity_SquareMile, "mi²"); + } + return {}; +} + +UnitSystem::TranslateResult UnitSystem::translateAngle(QuantityAngle angle, AngleUnit unit) +{ + switch (unit) { + case AngleUnit::Radian: return UnitSystem::radians(angle); + case AngleUnit::Degree: return UnitSystem::degrees(angle); + } + return {}; +} + UnitSystem::TranslateResult UnitSystem::radians(QuantityAngle angle) { return { angle.value(), "rad", 1. }; diff --git a/src/base/unit_system.h b/src/base/unit_system.h index 01db3134..b6fe0c19 100644 --- a/src/base/unit_system.h +++ b/src/base/unit_system.h @@ -13,16 +13,14 @@ namespace Mayo { class UnitSystem { public: - enum Schema { - SI, - ImperialUK - }; + enum Schema { SI, ImperialUK }; struct TranslateResult { double value; const char* strUnit; // UTF8 double factor; constexpr operator double() const { return this->value; } + constexpr operator bool() const { return this->strUnit != nullptr; } }; template @@ -32,6 +30,10 @@ class UnitSystem { static TranslateResult translate(Schema schema, double value, Unit unit); static TranslateResult parseQuantity(std::string_view strQuantity, Unit* ptrUnit = nullptr); + static TranslateResult translateLength(QuantityLength length, LengthUnit unit); + static TranslateResult translateArea(QuantityArea area, AreaUnit unit); + static TranslateResult translateAngle(QuantityAngle angle, AngleUnit unit); + static TranslateResult radians(QuantityAngle angle); static TranslateResult degrees(QuantityAngle angle); static TranslateResult meters(QuantityLength length); diff --git a/src/base/xcaf.cpp b/src/base/xcaf.cpp index d9b06818..f2ccb8cb 100644 --- a/src/base/xcaf.cpp +++ b/src/base/xcaf.cpp @@ -164,6 +164,11 @@ bool XCaf::isShapeSub(const TDF_Label& lbl) return XCAFDoc_ShapeTool::IsSubShape(lbl); } +bool XCaf::isShapeSubOf(const TDF_Label& lbl, const TopoDS_Shape& shape) +{ + return this->shapeTool()->IsSubShape(lbl, shape); +} + bool XCaf::hasShapeColor(const TDF_Label& lbl) const { Handle_XCAFDoc_ColorTool tool = this->colorTool(); @@ -194,6 +199,18 @@ Quantity_Color XCaf::shapeColor(const TDF_Label& lbl) const return color; } +TDF_Label XCaf::findShapeLabel(const TopoDS_Shape& shape, FindShapeLabelFlags flags) const +{ + TDF_Label label; + const bool findInstance = (flags & FindShapeLabel_Instance) != 0; + const bool findComponent = (flags & FindShapeLabel_Component) != 0; + const bool findSubShape = (flags & FindShapeLabel_SubShape) != 0; + if (this->shapeTool()->Search(shape, label, findInstance, findComponent, findSubShape)) + return label; + + return {}; +} + TopLoc_Location XCaf::shapeReferenceLocation(const TDF_Label& lbl) { return XCAFDoc_ShapeTool::GetLocation(lbl); @@ -235,12 +252,10 @@ TopLoc_Location XCaf::shapeAbsoluteLocation(TreeNodeId nodeId) const TopLoc_Location XCaf::shapeAbsoluteLocation(const Tree& modelTree, TreeNodeId nodeId) { TopLoc_Location absoluteLoc; - TreeNodeId it = nodeId; - while (it != 0) { + for (TreeNodeId it = nodeId; it != 0; it = modelTree.nodeParent(it)) { const TDF_Label& nodeLabel = modelTree.nodeData(it); const TopLoc_Location nodeLoc = XCaf::shapeReferenceLocation(nodeLabel); absoluteLoc = nodeLoc * absoluteLoc; - it = modelTree.nodeParent(it); } return absoluteLoc; @@ -281,7 +296,7 @@ XCaf::ValidationProperties XCaf::validationProperties(const TDF_Label& lbl) else if (&attrId == &XCAFDoc_Area::GetID()) { const auto& area = static_cast(*ptrAttr); props.hasArea = true; - props.area = area.Get() * Quantity_SquaredMillimeter; + props.area = area.Get() * Quantity_SquareMillimeter; } else if (&attrId == &XCAFDoc_Volume::GetID()) { const auto& volume = static_cast(*ptrAttr); diff --git a/src/base/xcaf.h b/src/base/xcaf.h index 3f8fbc6d..8bc3b8a9 100644 --- a/src/base/xcaf.h +++ b/src/base/xcaf.h @@ -57,9 +57,28 @@ class XCaf { static bool isShapeCompound(const TDF_Label& lbl); static bool isShapeSub(const TDF_Label& lbl); + // Is 'shape' a subshape of the shape stored in 'lbl' ? + bool isShapeSubOf(const TDF_Label& lbl, const TopoDS_Shape& shape); + bool hasShapeColor(const TDF_Label& lbl) const; Quantity_Color shapeColor(const TDF_Label& lbl) const; + // Various flags for findShapeLabel() + enum FindShapeLabelFlag { + // findShapeLabel() will first try to find the shape among the top-level shapes + FindShapeLabel_Instance = 0x01, + // findShapeLabel() will try to find the shape find the shape among the components of assemblies + FindShapeLabel_Component = 0x02, + // findShapeLabel() will try tries to find a shape as a subshape of top-level simple shapes + FindShapeLabel_SubShape = 0x04, + // Enables all flags + FindShapeLabel_All = 0xFF + }; + using FindShapeLabelFlags = unsigned; + + // Finds a (sub) shape in the document, returns null label if not found + TDF_Label findShapeLabel(const TopoDS_Shape& shape, FindShapeLabelFlags flags = FindShapeLabel_All) const; + TopLoc_Location shapeAbsoluteLocation(TreeNodeId nodeId) const; static TopLoc_Location shapeAbsoluteLocation(const Tree& modelTree, TreeNodeId nodeId); static TopLoc_Location shapeReferenceLocation(const TDF_Label& lbl); diff --git a/src/graphics/ais_text.cpp b/src/graphics/ais_text.cpp index 7d935620..46d7d838 100644 --- a/src/graphics/ais_text.cpp +++ b/src/graphics/ais_text.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -41,28 +42,22 @@ AIS_Text::AIS_Text(const TCollection_ExtendedString &text, const gp_Pnt& pos) Handle_Prs3d_TextAspect AIS_Text::presentationTextAspect(unsigned i) const { - if (this->isValidTextIndex(i)) - return m_textProps.at(i).m_aspect; - return Handle_Prs3d_TextAspect(); + return this->isValidTextIndex(i) ? m_textProps.at(i).m_aspect : Handle_Prs3d_TextAspect(); } Handle_Graphic3d_AspectText3d AIS_Text::graphicTextAspect(unsigned i) const { - if (this->isValidTextIndex(i)) - return m_textProps.at(i).m_aspect->Aspect(); - return Handle_Graphic3d_AspectText3d(); + return this->isValidTextIndex(i) ? m_textProps.at(i).m_aspect->Aspect() : Handle_Graphic3d_AspectText3d(); } gp_Pnt AIS_Text::position(unsigned i) const { - return this->isValidTextIndex(i) ? - m_textProps.at(i).m_position : gp_Pnt(); + return this->isValidTextIndex(i) ? m_textProps.at(i).m_position : gp_Pnt(); } TCollection_ExtendedString AIS_Text::text(unsigned i) const { - return this->isValidTextIndex(i) ? - m_textProps.at(i).m_text : TCollection_ExtendedString(); + return this->isValidTextIndex(i) ? m_textProps.at(i).m_text : TCollection_ExtendedString(); } bool AIS_Text::isValidTextIndex(unsigned i) const @@ -75,7 +70,7 @@ unsigned AIS_Text::textCount() const return static_cast(m_textProps.size()); } -void AIS_Text::addText(const TCollection_ExtendedString &text, const gp_Pnt& pos) +void AIS_Text::addText(const TCollection_ExtendedString& text, const gp_Pnt& pos) { TextProperties newProps; m_textProps.push_back(newProps); @@ -148,20 +143,21 @@ void AIS_Text::setDefaultTextStyle(Aspect_TypeOfStyleText style) } void AIS_Text::Compute( - const Handle(PrsMgr_PresentationManager3d)&, + const Handle(PrsMgr_PresentationManager)&, const Handle(Prs3d_Presentation)& pres, const int) { for (unsigned i = 0; i < this->textCount(); ++i) { Prs3d_Text::Draw( -#if OCC_VERSION_HEX >= 0x070600 +#if OCC_VERSION_HEX >= 0x070400 pres->CurrentGroup(), #else - pres, + Prs3d_Root::CurrentGroup(pres), #endif this->presentationTextAspect(i), this->text(i), - this->position(i)); + this->position(i) + ); } } diff --git a/src/graphics/ais_text.h b/src/graphics/ais_text.h index 44296bfb..6c5a0f0a 100644 --- a/src/graphics/ais_text.h +++ b/src/graphics/ais_text.h @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include #include @@ -58,7 +58,7 @@ class AIS_Text : public AIS_InteractiveObject { protected: void Compute( - const Handle(PrsMgr_PresentationManager3d)& pm, + const Handle(PrsMgr_PresentationManager)& pm, const Handle(Prs3d_Presentation)& pres, const int mode) override; diff --git a/src/graphics/graphics_create_virtual_window.cpp b/src/graphics/graphics_create_virtual_window.cpp index e99b3e1a..cab0de55 100644 --- a/src/graphics/graphics_create_virtual_window.cpp +++ b/src/graphics/graphics_create_virtual_window.cpp @@ -10,8 +10,7 @@ // #defines constants like "None" which causes name clash with GuiDocument::ViewTrihedronMode::None // -- -#include -#if defined(Q_OS_WIN) +#if defined(_WIN32) # include #endif @@ -19,11 +18,13 @@ #include #include -#if defined(Q_OS_WIN) +#if defined(_WIN32) # include # include -#elif defined(Q_OS_MACOS) // Q_OS_DARWIN? +#elif defined(__APPLE__) # include +#elif defined(__ANDROID__) +# include #else # include #endif @@ -32,15 +33,23 @@ namespace Mayo { Handle_Aspect_Window graphicsCreateVirtualWindow(const Handle_Graphic3d_GraphicDriver& gfxDriver, int wndWidth, int wndHeight) { -#if defined(Q_OS_WIN) +#if defined(_WIN32) MAYO_UNUSED(gfxDriver); // Create a "virtual" WNT window being a pure WNT window redefined to be never shown - auto cursor = LoadCursor(NULL, IDC_ARROW); - auto wClass = new WNT_WClass("GW3D_Class", (void*)DefWindowProcW, CS_VREDRAW | CS_HREDRAW, 0, 0, cursor); + static Handle_WNT_WClass wClass; + if (wClass.IsNull()) { + auto cursor = LoadCursor(NULL, IDC_ARROW); + wClass = new WNT_WClass("GW3D_Class", nullptr, CS_VREDRAW | CS_HREDRAW, 0, 0, cursor); + } + auto wnd = new WNT_Window("", wClass, WS_POPUP, 0, 0, wndWidth, wndHeight, Quantity_NOC_BLACK); -#elif defined(Q_OS_MACOS) +#elif defined(__APPLE__) MAYO_UNUSED(gfxDriver); auto wnd = new Cocoa_Window("", 0, 0, wndWidth, wndHeight); +#elif defined(__ANDROID__) + MAYO_UNUSED(gfxDriver); + auto wnd = new Aspect_NeutralWindow; + wnd->SetSize(wndWidth, wndHeight); #else auto displayConn = gfxDriver->GetDisplayConnection(); auto wnd = new Xw_Window(displayConn, "", 0, 0, wndWidth, wndHeight); diff --git a/src/graphics/graphics_mesh_object_driver.cpp b/src/graphics/graphics_mesh_object_driver.cpp new file mode 100644 index 00000000..1dd4dc93 --- /dev/null +++ b/src/graphics/graphics_mesh_object_driver.cpp @@ -0,0 +1,234 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "graphics_mesh_object_driver.h" + +#include "../base/caf_utils.h" +#include "../base/cpp_utils.h" +#include "../base/data_triangulation.h" +#include "../base/property_builtins.h" +#include "../base/xcaf.h" +#include "graphics_mesh_data_source.h" +#include "graphics_utils.h" + +#include +#include +#include +#include +#include +#include +#include + +namespace Mayo { + +namespace { +struct GraphicsMeshObjectDriverI18N { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::GraphicsMeshObjectDriver) }; +} // namespace + +GraphicsMeshObjectDriver::GraphicsMeshObjectDriver() +{ + this->setDisplayModes({ + { MeshVS_DMF_WireFrame, GraphicsMeshObjectDriverI18N::textId("Mesh_Wireframe") }, + { MeshVS_DMF_Shading, GraphicsMeshObjectDriverI18N::textId("Mesh_Shaded") }, + { MeshVS_DMF_Shrink, GraphicsMeshObjectDriverI18N::textId("Mesh_Shrink") } // MeshVS_DA_ShrinkCoeff + }); + this->setDefaultDisplayMode(MeshVS_DMF_Shading); +} + +GraphicsObjectDriver::Support GraphicsMeshObjectDriver::supportStatus(const TDF_Label& label) const +{ + if (CafUtils::hasAttribute(label)) + return Support::Complete; + + if (XCaf::isShape(label)) { + const TopoDS_Shape shape = XCaf::shape(label); + if (shape.ShapeType() == TopAbs_FACE) + return Support::Partial; + } + + return Support::None; +} + +GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) const +{ + Handle_Poly_Triangulation polyTri; + Span spanNodeColor; + //const TopLoc_Location* ptrLocationPolyTri = nullptr; + auto attrTriangulation = CafUtils::findAttribute(label); + if (attrTriangulation) { + polyTri = attrTriangulation->Get(); + spanNodeColor = attrTriangulation->nodeColors(); + } + else if (XCaf::isShape(label)) { + const TopoDS_Shape shape = XCaf::shape(label); + if (shape.ShapeType() == TopAbs_FACE) { + auto tface = Handle_BRep_TFace::DownCast(shape.TShape()); + if (tface) { + polyTri = tface->Triangulation(); + //ptrLocationPolyTri = &shape.Location(); + } + } + } + + if (polyTri) { + Handle_MeshVS_Mesh object = new MeshVS_Mesh; + object->SetDataSource(new GraphicsMeshDataSource(polyTri)); + // meshVisu->AddBuilder(..., false); -> No selection + if (!spanNodeColor.empty()) { + auto meshPrsBuilder = new MeshVS_NodalColorPrsBuilder(object, MeshVS_DMF_NodalColorDataPrs | MeshVS_DMF_OCCMask); + for (int i = 0; CppUtils::cmpLess(i, spanNodeColor.size()); ++i) + meshPrsBuilder->SetColor(i + 1, spanNodeColor[i]); + + object->AddBuilder(meshPrsBuilder, true); + } + else { + object->AddBuilder(new MeshVS_MeshPrsBuilder(object), true); + } + + // -- MeshVS_DrawerAttribute + object->GetDrawer()->SetBoolean(MeshVS_DA_ShowEdges, defaultValues().showEdges); + object->GetDrawer()->SetBoolean(MeshVS_DA_DisplayNodes, defaultValues().showNodes); + object->GetDrawer()->SetColor(MeshVS_DA_InteriorColor, defaultValues().color); + object->GetDrawer()->SetMaterial( + MeshVS_DA_FrontMaterial, Graphic3d_MaterialAspect(defaultValues().material)); + object->GetDrawer()->SetColor(MeshVS_DA_EdgeColor, defaultValues().edgeColor); + object->GetDrawer()->SetBoolean(MeshVS_DA_ColorReflection, true); + object->SetDisplayMode(MeshVS_DMF_Shading); + + //object->SetHilightMode(MeshVS_DMF_WireFrame); + object->SetMeshSelMethod(MeshVS_MSM_PRECISE); + + object->SetOwner(this); + return object; + } + + return {}; +} + +void GraphicsMeshObjectDriver::applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const +{ + this->throwIf_differentDriver(object); + this->throwIf_invalidDisplayMode(mode); + GraphicsUtils::AisObject_contextPtr(object)->SetDisplayMode(object, mode, false); +} + +Enumeration::Value GraphicsMeshObjectDriver::currentDisplayMode(const GraphicsObjectPtr& object) const +{ + this->throwIf_differentDriver(object); + return object->DisplayMode(); +} + +class GraphicsMeshObjectDriver::ObjectProperties : public PropertyGroupSignals { +public: + ObjectProperties(Span spanObject) + { + NCollection_Vec3 sumColor = {}; + NCollection_Vec3 sumEdgeColor = {}; + int countShowEdges = 0; + int countShowNodes = 0; + for (const GraphicsObjectPtr& object : spanObject) { + auto meshVisu = Handle_MeshVS_Mesh::DownCast(object); + // Color + Quantity_Color color; + meshVisu->GetDrawer()->GetColor(MeshVS_DA_InteriorColor, color); + sumColor += color; + // Edge color + meshVisu->GetDrawer()->GetColor(MeshVS_DA_EdgeColor, color); + sumEdgeColor += color; + // Show edges + bool boolVal; + meshVisu->GetDrawer()->GetBoolean(MeshVS_DA_ShowEdges, boolVal); + countShowEdges += boolVal ? 1 : 0; + // Show nodes + meshVisu->GetDrawer()->GetBoolean(MeshVS_DA_DisplayNodes, boolVal); + countShowNodes += boolVal ? 1 : 0; + + m_vecMeshVisu.push_back(meshVisu); + } + + auto fnCheckState = [&](int count) { + if (count == 0) + return CheckState::Off; + else + return CppUtils::cmpEqual(count, spanObject.size()) ? CheckState::On : CheckState::Partially; + }; + + // Init properties + Mayo_PropertyChangedBlocker(this); + + m_propertyColor.setValue(Quantity_Color(sumColor / float(spanObject.size()))); + m_propertyEdgeColor.setValue(Quantity_Color(sumEdgeColor / float(spanObject.size()))); + m_propertyShowEdges.setValue(fnCheckState(countShowEdges)); + m_propertyShowNodes.setValue(fnCheckState(countShowNodes)); + } + + void onPropertyChanged(Property* prop) override + { + auto fnRedisplay = [](const GraphicsObjectPtr& object) { + object->Redisplay(true); // All modes + }; + + if (prop == &m_propertyShowEdges) { + if (m_propertyShowEdges.value() != CheckState::Partially) { + for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + meshVisu->GetDrawer()->SetBoolean(MeshVS_DA_ShowEdges, m_propertyShowEdges.value() == CheckState::On); + fnRedisplay(meshVisu); + } + } + } + else if (prop == &m_propertyShowNodes) { + if (m_propertyShowNodes.value() != CheckState::Partially) { + for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + meshVisu->GetDrawer()->SetBoolean(MeshVS_DA_DisplayNodes, m_propertyShowNodes.value() == CheckState::On); + fnRedisplay(meshVisu); + } + } + } + else if (prop == &m_propertyColor) { + for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + meshVisu->GetDrawer()->SetColor(MeshVS_DA_InteriorColor, m_propertyColor); + fnRedisplay(meshVisu); + } + } + else if (prop == &m_propertyEdgeColor) { + for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { + meshVisu->GetDrawer()->SetColor(MeshVS_DA_EdgeColor, m_propertyEdgeColor); + fnRedisplay(meshVisu); + } + } + + PropertyGroupSignals::onPropertyChanged(prop); + } + + std::vector m_vecMeshVisu; + PropertyOccColor m_propertyColor{ this, GraphicsMeshObjectDriverI18N::textId("color") }; + PropertyOccColor m_propertyEdgeColor{ this, GraphicsMeshObjectDriverI18N::textId("edgeColor") }; + PropertyCheckState m_propertyShowEdges{ this, GraphicsMeshObjectDriverI18N::textId("showEdges") }; + PropertyCheckState m_propertyShowNodes{ this, GraphicsMeshObjectDriverI18N::textId("showNodes") }; +}; + +std::unique_ptr +GraphicsMeshObjectDriver::properties(Span spanObject) const +{ + this->throwIf_differentDriver(spanObject); + return std::make_unique(spanObject); +} + +namespace Internal { + +Q_GLOBAL_STATIC(GraphicsMeshObjectDriver::DefaultValues, graphicsMeshDefaultValues) + +} // namespace Internal + +const GraphicsMeshObjectDriver::DefaultValues& GraphicsMeshObjectDriver::defaultValues() { + return *Internal::graphicsMeshDefaultValues; +} + +void GraphicsMeshObjectDriver::setDefaultValues(const DefaultValues& values) { + *Internal::graphicsMeshDefaultValues = values; +} +} +// namespace Mayo diff --git a/src/graphics/graphics_mesh_object_driver.h b/src/graphics/graphics_mesh_object_driver.h new file mode 100644 index 00000000..2f1d7ed6 --- /dev/null +++ b/src/graphics/graphics_mesh_object_driver.h @@ -0,0 +1,44 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "graphics_object_driver.h" + +namespace Mayo { + +class GraphicsMeshObjectDriver; +DEFINE_STANDARD_HANDLE(GraphicsMeshObjectDriver, GraphicsObjectDriver) +using GraphicsMeshObjectDriverPtr = Handle(GraphicsMeshObjectDriver); + +// Provides creation and configuration of graphics objects for meshes(triangulations) +class GraphicsMeshObjectDriver : public GraphicsObjectDriver { +public: + GraphicsMeshObjectDriver(); + + Support supportStatus(const TDF_Label& label) const override; + GraphicsObjectPtr createObject(const TDF_Label& label) const override; + void applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const override; + Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const override; + std::unique_ptr properties(Span spanObject) const override; + + struct DefaultValues { + bool showEdges = false; + bool showNodes = false; + Graphic3d_NameOfMaterial material = Graphic3d_NOM_PLASTER; + Quantity_Color color = Quantity_NOC_BISQUE; + Quantity_Color edgeColor = Quantity_NOC_BLACK; + }; + static const DefaultValues& defaultValues(); + static void setDefaultValues(const DefaultValues& values); + + DEFINE_STANDARD_RTTI_INLINE(GraphicsMeshObjectDriver, GraphicsObjectDriver) + +private: + class ObjectProperties; +}; + +} // namespace Mayo diff --git a/src/graphics/graphics_object_base_property_group.cpp b/src/graphics/graphics_object_base_property_group.cpp deleted file mode 100644 index 467fa64a..00000000 --- a/src/graphics/graphics_object_base_property_group.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "graphics_object_base_property_group.h" -#include "graphics_object_driver.h" -#include "graphics_utils.h" -#include "../base/application.h" -#include "../base/application_item_selection_model.h" -#include "../base/text_id.h" - -namespace Mayo { - -GraphicsObjectBasePropertyGroup::GraphicsObjectBasePropertyGroup(Span spanObject) - : m_vecObject(spanObject.begin(), spanObject.end())/*, - m_propertyVisibleState(this, textId("visible"))*/ -{ - // Init properties - Mayo_PropertyChangedBlocker(this); -#if 0 - int visibleCount = 0; - for (const GraphicsObjectPtr& object : spanObject) { - if (GraphicsUtils::AisObject_isVisible(object)) - ++visibleCount; - } - - if (visibleCount == 0) - m_propertyVisibleState.setValue(Qt::Unchecked); - else - m_propertyVisibleState.setValue(visibleCount == spanObject.size() ? Qt::Checked : Qt::PartiallyChecked); -#endif -} - -void GraphicsObjectBasePropertyGroup::onPropertyChanged(Property* prop) -{ -#if 0 - if (prop == &m_propertyVisibleState) { - if (m_propertyVisibleState != Qt::PartiallyChecked) { - const bool isVisible = m_propertyVisibleState == Qt::Checked; - for (const GraphicsObjectPtr& object : m_vecObject) - GraphicsUtils::AisObject_setVisible(object, isVisible); - - emit this->visibilityToggled(isVisible); - } - } -#endif - - PropertyGroupSignals::onPropertyChanged(prop); -} - -} // namespace Mayo diff --git a/src/graphics/graphics_object_base_property_group.h b/src/graphics/graphics_object_base_property_group.h deleted file mode 100644 index e8f4063e..00000000 --- a/src/graphics/graphics_object_base_property_group.h +++ /dev/null @@ -1,31 +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 "../base/span.h" -#include "../base/property_builtins.h" -#include "graphics_object_ptr.h" -#include - -namespace Mayo { - -class GraphicsObjectBasePropertyGroup : public PropertyGroupSignals { - Q_OBJECT - MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::GraphicsObjectBasePropertyGroup) -public: - GraphicsObjectBasePropertyGroup(Span spanObject); - void onPropertyChanged(Property* prop) override; - -//signals: -// void visibilityToggled(bool on); - -private: - std::vector m_vecObject; - //PropertyCheckState m_propertyVisibleState; -}; - -} // namespace Mayo diff --git a/src/graphics/graphics_object_driver.cpp b/src/graphics/graphics_object_driver.cpp index 9b336384..35b01d6b 100644 --- a/src/graphics/graphics_object_driver.cpp +++ b/src/graphics/graphics_object_driver.cpp @@ -6,30 +6,6 @@ #include "graphics_object_driver.h" -#include "../base/document.h" -#include "../base/caf_utils.h" -#include "../base/property_enumeration.h" -#include "graphics_object_base_property_group.h" -#include "graphics_mesh_data_source.h" -#include "graphics_scene.h" -#include "graphics_utils.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - namespace Mayo { namespace { struct GraphicsObjectDriverI18N { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::GraphicsObjectDriver) }; } @@ -58,7 +34,7 @@ GraphicsObjectDriverPtr GraphicsObjectDriver::getCommon(SpandisplayModes().findIndex(mode) == -1) + if (this->displayModes().findIndexByValue(mode) == -1) throw std::invalid_argument("Invalid display mode"); } @@ -74,319 +50,4 @@ void GraphicsObjectDriver::throwIf_differentDriver(Span this->throwIf_differentDriver(object); } -GraphicsShapeObjectDriver::GraphicsShapeObjectDriver() -{ - this->setDisplayModes({ - { DisplayMode_Wireframe, GraphicsObjectDriverI18N::textId("Shape_Wireframe") }, - { DisplayMode_HiddenLineRemoval, GraphicsObjectDriverI18N::textId("Shape_HiddenLineRemoval") }, - { DisplayMode_Shaded, GraphicsObjectDriverI18N::textId("Shape_Shaded") }, - { DisplayMode_ShadedWithFaceBoundary, GraphicsObjectDriverI18N::textId("Shape_ShadedWithFaceBoundary") } - }); - this->setDefaultDisplayMode(DisplayMode_ShadedWithFaceBoundary); -} - -GraphicsObjectDriver::Support GraphicsShapeObjectDriver::supportStatus(const TDF_Label& label) const -{ - if (XCaf::isShape(label)) - return Support::Complete; - - // TODO TNaming_Shape ? - // TDataXtd_Shape ? - - if (CafUtils::hasAttribute(label)) { - //return Support::Partial; - } - - return Support::None; -} - -GraphicsObjectPtr GraphicsShapeObjectDriver::createObject(const TDF_Label& label) const -{ - if (XCaf::isShape(label)) { - auto object = new XCAFPrs_AISObject(label); - object->SetDisplayMode(AIS_Shaded); - object->SetMaterial(Graphic3d_NOM_PLASTER); - object->Attributes()->SetFaceBoundaryDraw(true); - object->Attributes()->SetFaceBoundaryAspect( - new Prs3d_LineAspect(Quantity_NOC_BLACK, Aspect_TOL_SOLID, 1.)); - object->Attributes()->SetIsoOnTriangulation(true); - object->SetOwner(this); - return object; - } - else if (CafUtils::hasAttribute(label)) { - } - - return {}; -} - -void GraphicsShapeObjectDriver::applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const -{ - this->throwIf_differentDriver(object); - this->throwIf_invalidDisplayMode(mode); - - if (mode == this->currentDisplayMode(object)) - return; - - AIS_InteractiveContext* context = GraphicsUtils::AisObject_contextPtr(object); - if (!context) - return; - - auto fnSetViewComputedMode = [=](bool on) { - V3d_ListOfViewIterator viewIter = context->CurrentViewer()->DefinedViewIterator(); - while (viewIter.More()) { - viewIter.Value()->SetComputedMode(on); - viewIter.Next(); - } - }; - - if (mode == DisplayMode_HiddenLineRemoval) { - context->DefaultDrawer()->SetTypeOfHLR(Prs3d_TOH_PolyAlgo); - context->DefaultDrawer()->EnableDrawHiddenLine(); - fnSetViewComputedMode(true); - } - else { - context->DefaultDrawer()->SetTypeOfHLR(Prs3d_TOH_NotSet); - context->DefaultDrawer()->DisableDrawHiddenLine(); - fnSetViewComputedMode(false); - const AIS_DisplayMode aisDispMode = mode == DisplayMode_Wireframe ? AIS_WireFrame : AIS_Shaded; - const bool showFaceBounds = mode == DisplayMode_ShadedWithFaceBoundary; - if (object->DisplayMode() != aisDispMode) - context->SetDisplayMode(object, aisDispMode, false); - - if (object->Attributes()->FaceBoundaryDraw() != showFaceBounds) { - object->Attributes()->SetFaceBoundaryDraw(showFaceBounds); - auto aisLink = Handle_AIS_ConnectedInteractive::DownCast(object); - if (aisLink && aisLink->HasConnection()) { - aisLink->ConnectedTo()->Attributes()->SetFaceBoundaryDraw(showFaceBounds); - aisLink->ConnectedTo()->Redisplay(true); - } - else { - object->Redisplay(true); - } - } - } - - // context->UpdateCurrentViewer(); -} - -Enumeration::Value GraphicsShapeObjectDriver::currentDisplayMode(const GraphicsObjectPtr& object) const -{ - this->throwIf_differentDriver(object); - if (GraphicsUtils::AisObject_contextPtr(object)->DrawHiddenLine()) - return DisplayMode_HiddenLineRemoval; - - const int displayMode = object->DisplayMode(); - if (displayMode == AIS_WireFrame) - return DisplayMode_Wireframe; - - if (displayMode == AIS_Shaded) { - return object->Attributes()->FaceBoundaryDraw() ? - DisplayMode_ShadedWithFaceBoundary : - DisplayMode_Shaded; - } - - return -1; -} - -std::unique_ptr -GraphicsShapeObjectDriver::properties(Span spanObject) const -{ - this->throwIf_differentDriver(spanObject); - //return std::make_unique(spanObject); - return {}; -} - -GraphicsMeshObjectDriver::GraphicsMeshObjectDriver() -{ - this->setDisplayModes({ - { MeshVS_DMF_WireFrame, GraphicsObjectDriverI18N::textId("Mesh_Wireframe") }, - { MeshVS_DMF_Shading, GraphicsObjectDriverI18N::textId("Mesh_Shaded") }, - { MeshVS_DMF_Shrink, GraphicsObjectDriverI18N::textId("Mesh_Shrink") } // MeshVS_DA_ShrinkCoeff - }); - this->setDefaultDisplayMode(MeshVS_DMF_Shading); -} - -GraphicsObjectDriver::Support GraphicsMeshObjectDriver::supportStatus(const TDF_Label& label) const -{ - if (CafUtils::hasAttribute(label)) - return Support::Complete; - - if (XCaf::isShape(label)) { - const TopoDS_Shape shape = XCaf::shape(label); - if (shape.ShapeType() == TopAbs_FACE) - return Support::Partial; - } - - return Support::None; -} - -GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) const -{ - Handle_Poly_Triangulation polyTri; - //const TopLoc_Location* ptrLocationPolyTri = nullptr; - auto attrTriangulation = CafUtils::findAttribute(label); - if (attrTriangulation) { - polyTri = attrTriangulation->Get(); - } - else if (XCaf::isShape(label)) { - const TopoDS_Shape shape = XCaf::shape(label); - if (shape.ShapeType() == TopAbs_FACE) { - auto tface = Handle_BRep_TFace::DownCast(shape.TShape()); - if (tface) { - polyTri = tface->Triangulation(); - //ptrLocationPolyTri = &shape.Location(); - } - } - } - - if (polyTri) { - Handle_MeshVS_Mesh object = new MeshVS_Mesh; - object->SetDataSource(new GraphicsMeshDataSource(polyTri)); - // meshVisu->AddBuilder(..., false); -> No selection - object->AddBuilder(new MeshVS_MeshPrsBuilder(object), true); - - // -- MeshVS_DrawerAttribute - object->GetDrawer()->SetBoolean(MeshVS_DA_ShowEdges, defaultValues().showEdges); - object->GetDrawer()->SetBoolean(MeshVS_DA_DisplayNodes, defaultValues().showNodes); - object->GetDrawer()->SetMaterial(MeshVS_DA_FrontMaterial, Graphic3d_NOM_PLASTIC); - object->GetDrawer()->SetColor(MeshVS_DA_InteriorColor, defaultValues().color); - object->GetDrawer()->SetMaterial( - MeshVS_DA_FrontMaterial, Graphic3d_MaterialAspect(defaultValues().material)); - object->GetDrawer()->SetColor(MeshVS_DA_EdgeColor, defaultValues().edgeColor); - object->SetDisplayMode(MeshVS_DMF_Shading); - - //object->SetHilightMode(MeshVS_DMF_WireFrame); - object->SetMeshSelMethod(MeshVS_MSM_PRECISE); - - object->SetOwner(this); - return object; - } - - return {}; -} - -void GraphicsMeshObjectDriver::applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const -{ - this->throwIf_differentDriver(object); - this->throwIf_invalidDisplayMode(mode); - GraphicsUtils::AisObject_contextPtr(object)->SetDisplayMode(object, mode, false); -} - -Enumeration::Value GraphicsMeshObjectDriver::currentDisplayMode(const GraphicsObjectPtr& object) const -{ - this->throwIf_differentDriver(object); - return object->DisplayMode(); -} - -class GraphicsMeshObjectDriver::ObjectProperties : public GraphicsObjectBasePropertyGroup { - MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::GraphicsMeshObjectDriver_ObjectProperties) -public: - ObjectProperties(Span spanObject) - : GraphicsObjectBasePropertyGroup(spanObject) - { - NCollection_Vec3 sumColor = {}; - NCollection_Vec3 sumEdgeColor = {}; - int countShowEdges = 0; - int countShowNodes = 0; - for (const GraphicsObjectPtr& object : spanObject) { - auto meshVisu = Handle_MeshVS_Mesh::DownCast(object); - // Color - Quantity_Color color; - meshVisu->GetDrawer()->GetColor(MeshVS_DA_InteriorColor, color); - sumColor += color; - // Edge color - meshVisu->GetDrawer()->GetColor(MeshVS_DA_EdgeColor, color); - sumEdgeColor += color; - // Show edges - bool boolVal; - meshVisu->GetDrawer()->GetBoolean(MeshVS_DA_ShowEdges, boolVal); - countShowEdges += boolVal ? 1 : 0; - // Show nodes - meshVisu->GetDrawer()->GetBoolean(MeshVS_DA_DisplayNodes, boolVal); - countShowNodes += boolVal ? 1 : 0; - - m_vecMeshVisu.push_back(meshVisu); - } - - auto fnCheckState = [&](int count) { - if (count == 0) - return CheckState::Off; - else - return count == spanObject.size() ? CheckState::On : CheckState::Partially; - }; - - // Init properties - Mayo_PropertyChangedBlocker(this); - - m_propertyColor.setValue(Quantity_Color(sumColor / float(spanObject.size()))); - m_propertyEdgeColor.setValue(Quantity_Color(sumEdgeColor / float(spanObject.size()))); - m_propertyShowEdges.setValue(fnCheckState(countShowEdges)); - m_propertyShowNodes.setValue(fnCheckState(countShowNodes)); - } - - void onPropertyChanged(Property* prop) override { - auto fnRedisplay = [](const GraphicsObjectPtr& object) { - object->Redisplay(true); // All modes - }; - - if (prop == &m_propertyShowEdges) { - if (m_propertyShowEdges.value() != CheckState::Partially) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { - meshVisu->GetDrawer()->SetBoolean(MeshVS_DA_ShowEdges, m_propertyShowEdges.value() == CheckState::On); - fnRedisplay(meshVisu); - } - } - } - else if (prop == &m_propertyShowNodes) { - if (m_propertyShowNodes.value() != CheckState::Partially) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { - meshVisu->GetDrawer()->SetBoolean(MeshVS_DA_DisplayNodes, m_propertyShowNodes.value() == CheckState::On); - fnRedisplay(meshVisu); - } - } - } - else if (prop == &m_propertyColor) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { - meshVisu->GetDrawer()->SetColor(MeshVS_DA_InteriorColor, m_propertyColor); - fnRedisplay(meshVisu); - } - } - else if (prop == &m_propertyEdgeColor) { - for (const Handle_MeshVS_Mesh& meshVisu : m_vecMeshVisu) { - meshVisu->GetDrawer()->SetColor(MeshVS_DA_EdgeColor, m_propertyEdgeColor); - fnRedisplay(meshVisu); - } - } - - GraphicsObjectBasePropertyGroup::onPropertyChanged(prop); - } - - std::vector m_vecMeshVisu; - PropertyOccColor m_propertyColor{ this, textId("color") }; - PropertyOccColor m_propertyEdgeColor{ this, textId("edgeColor") }; - PropertyCheckState m_propertyShowEdges{ this, textId("showEdges") }; - PropertyCheckState m_propertyShowNodes{ this, textId("showNodes") }; -}; - -std::unique_ptr -GraphicsMeshObjectDriver::properties(Span spanObject) const -{ - this->throwIf_differentDriver(spanObject); - return std::make_unique(spanObject); -} - -namespace Internal { - -Q_GLOBAL_STATIC(GraphicsMeshObjectDriver::DefaultValues, graphicsMeshDefaultValues) - -} // namespace Internal - -const GraphicsMeshObjectDriver::DefaultValues& GraphicsMeshObjectDriver::defaultValues() { - return *Internal::graphicsMeshDefaultValues; -} - -void GraphicsMeshObjectDriver::setDefaultValues(const DefaultValues& values) { - *Internal::graphicsMeshDefaultValues = values; -} - } // namespace Mayo diff --git a/src/graphics/graphics_object_driver.h b/src/graphics/graphics_object_driver.h index 0fc8e340..86a67757 100644 --- a/src/graphics/graphics_object_driver.h +++ b/src/graphics/graphics_object_driver.h @@ -7,7 +7,6 @@ #pragma once #include "graphics_object_ptr.h" -#include "graphics_object_base_property_group.h" #include "../base/enumeration.h" #include "../base/property.h" #include "../base/span.h" @@ -20,16 +19,13 @@ namespace Mayo { class GraphicsObjectDriver; DEFINE_STANDARD_HANDLE(GraphicsObjectDriver, Standard_Transient) -using GraphicsObjectDriverPtr = opencascade::handle; +using GraphicsObjectDriverPtr = Handle(GraphicsObjectDriver); +// Provides creation and configuration of graphics objects of a specific type +// Each graphics object "knows" the driver which created it: use function GraphicsObjectDriver::get() class GraphicsObjectDriver : public Standard_Transient { public: - enum Support { - None, - Partial, - Complete - }; - + enum Support { None, Partial, Complete }; virtual Support supportStatus(const TDF_Label& label) const = 0; virtual GraphicsObjectPtr createObject(const TDF_Label& label) const = 0; @@ -39,7 +35,7 @@ class GraphicsObjectDriver : public Standard_Transient { virtual void applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const = 0; virtual Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const = 0; - virtual std::unique_ptr properties(Span spanObject) const = 0; + virtual std::unique_ptr properties(Span spanObject) const = 0; static GraphicsObjectDriverPtr get(const GraphicsObjectPtr& object); static GraphicsObjectDriverPtr getCommon(Span spanObject); @@ -58,46 +54,4 @@ class GraphicsObjectDriver : public Standard_Transient { Enumeration::Value m_defaultDisplayMode = -1; }; -class GraphicsShapeObjectDriver : public GraphicsObjectDriver { -public: - GraphicsShapeObjectDriver(); - - Support supportStatus(const TDF_Label& label) const override; - GraphicsObjectPtr createObject(const TDF_Label& label) const override; - void applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const override; - Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const override; - std::unique_ptr properties(Span spanObject) const override; - - enum DisplayMode { - DisplayMode_Wireframe, - DisplayMode_HiddenLineRemoval, - DisplayMode_Shaded, - DisplayMode_ShadedWithFaceBoundary - }; -}; - -class GraphicsMeshObjectDriver : public GraphicsObjectDriver { -public: - GraphicsMeshObjectDriver(); - - Support supportStatus(const TDF_Label& label) const override; - GraphicsObjectPtr createObject(const TDF_Label& label) const override; - void applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const override; - Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const override; - std::unique_ptr properties(Span spanObject) const override; - - struct DefaultValues { - bool showEdges = false; - bool showNodes = false; - Graphic3d_NameOfMaterial material = Graphic3d_NOM_PLASTER; - Quantity_Color color = Quantity_NOC_BISQUE; - Quantity_Color edgeColor = Quantity_NOC_BLACK; - }; - static const DefaultValues& defaultValues(); - static void setDefaultValues(const DefaultValues& values); - -private: - class ObjectProperties; -}; - } // namespace Mayo diff --git a/src/graphics/graphics_object_driver_table.cpp b/src/graphics/graphics_object_driver_table.cpp deleted file mode 100644 index 0dfb9d21..00000000 --- a/src/graphics/graphics_object_driver_table.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "graphics_object_driver_table.h" -#include "graphics_object_driver.h" - -namespace Mayo { - -void GraphicsObjectDriverTable::addDriver(DriverPtr driver) -{ - m_vecDriver.push_back(driver); -} - -void GraphicsObjectDriverTable::addDriver(std::unique_ptr driver) -{ - m_vecDriver.push_back(driver.release()); -} - -GraphicsObjectPtr GraphicsObjectDriverTable::createObject(const TDF_Label& label) const -{ - GraphicsObjectDriver* driverPartialSupport = nullptr; - for (const DriverPtr& driver : m_vecDriver) { - const GraphicsObjectDriver::Support support = driver->supportStatus(label); - if (support == GraphicsObjectDriver::Support::Complete) - return driver->createObject(label); - - if (support == GraphicsObjectDriver::Support::Partial) - driverPartialSupport = driver.get(); - } - - if (driverPartialSupport) - return driverPartialSupport->createObject(label); - else - return {}; -} - -} // namespace Mayo diff --git a/src/graphics/graphics_object_driver_table.h b/src/graphics/graphics_object_driver_table.h deleted file mode 100644 index edb721dc..00000000 --- a/src/graphics/graphics_object_driver_table.h +++ /dev/null @@ -1,30 +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 "graphics_object_driver.h" -#include "../base/span.h" -#include -#include - -namespace Mayo { - -class GraphicsObjectDriverTable { -public: - using DriverPtr = GraphicsObjectDriverPtr; - - void addDriver(DriverPtr driver); - void addDriver(std::unique_ptr driver); - Span drivers() const { return m_vecDriver; } - - GraphicsObjectPtr createObject(const TDF_Label& label) const; - -private: - std::vector m_vecDriver; -}; - -} // namespace Mayo diff --git a/src/graphics/graphics_object_ptr.h b/src/graphics/graphics_object_ptr.h index 377598c5..c61a8658 100644 --- a/src/graphics/graphics_object_ptr.h +++ b/src/graphics/graphics_object_ptr.h @@ -11,5 +11,6 @@ namespace Mayo { using GraphicsObjectPtr = Handle(AIS_InteractiveObject); +using GraphicsObjectSelectionMode = int; } // namespace Mayo diff --git a/src/graphics/graphics_scene.cpp b/src/graphics/graphics_scene.cpp index 452ea31e..5265652e 100644 --- a/src/graphics/graphics_scene.cpp +++ b/src/graphics/graphics_scene.cpp @@ -31,6 +31,17 @@ static Handle_V3d_Viewer createOccViewer() // viewer->SetDefaultShadingModel(V3d_GOURAUD); viewer->SetDefaultLights(); viewer->SetLightOn(); +#if 0 + for (const Handle(Graphic3d_CLight)& light : viewer->DefinedLights()) { + if (light->Name() == "amblight") { + light->SetIntensity(0.2f); + } + else if (light->Name() == "headlight") { + light->SetIntensity(0.8f); + } + } +#endif + return viewer; } @@ -84,11 +95,6 @@ const opencascade::handle& GraphicsScene::v3dViewer() const return d->m_v3dViewer; } -const opencascade::handle& GraphicsScene::defaultPrs3dDrawer() const -{ - return d->m_aisContext->DefaultDrawer(); -} - const opencascade::handle& GraphicsScene::mainSelector() const { return d->m_aisContext->MainSelector(); @@ -99,6 +105,16 @@ bool GraphicsScene::hiddenLineDrawingOn() const return d->m_aisContext->DrawHiddenLine(); } +const opencascade::handle& GraphicsScene::drawerDefault() const +{ + return d->m_aisContext->DefaultDrawer(); +} + +const opencascade::handle& GraphicsScene::drawerHighlight(Prs3d_TypeOfHighlight style) const +{ + return d->m_aisContext->HighlightStyle(style); +} + void GraphicsScene::addObject(const GraphicsObjectPtr& object) { if (object) @@ -142,6 +158,11 @@ void GraphicsScene::deactivateObjectSelection(const Mayo::GraphicsObjectPtr &obj d->m_aisContext->Deactivate(object, mode); } +void GraphicsScene::deactivateObjectSelection(const GraphicsObjectPtr &object) +{ + d->m_aisContext->Deactivate(object); +} + void GraphicsScene::addSelectionFilter(const Handle_SelectMgr_Filter& filter) { d->m_aisContext->AddFilter(filter); @@ -212,8 +233,11 @@ GraphicsOwnerPtr GraphicsScene::firstSelectedOwner() const void GraphicsScene::clearSelection() { + const bool onEntryOwnerSelected = !this->firstSelectedOwner().IsNull(); d->m_aisContext->ClearDetected(false); d->m_aisContext->ClearSelected(false); + if (onEntryOwnerSelected) + emit this->selectionChanged(); } AIS_InteractiveContext* GraphicsScene::aisContextPtr() const @@ -239,10 +263,20 @@ void GraphicsScene::select() if (d->m_selectionMode == SelectionMode::None) return; - if (d->m_selectionMode == SelectionMode::Single) + if (d->m_selectionMode == SelectionMode::Single) { +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + d->m_aisContext->SelectDetected(AIS_SelectionScheme_Replace); +#else d->m_aisContext->Select(false); - else if (d->m_selectionMode == SelectionMode::Multi) +#endif + } + else if (d->m_selectionMode == SelectionMode::Multi) { +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 6, 0) + d->m_aisContext->SelectDetected(AIS_SelectionScheme_XOR); +#else d->m_aisContext->ShiftSelect(false); +#endif + } emit this->selectionChanged(); } @@ -261,12 +295,17 @@ const GraphicsOwnerPtr& GraphicsScene::currentHighlightedOwner() const #endif } -GraphicsScene::SelectionMode GraphicsScene::selectionMode() const { +GraphicsScene::SelectionMode GraphicsScene::selectionMode() const +{ return d->m_selectionMode; } -void GraphicsScene::setSelectionMode(GraphicsScene::SelectionMode mode) { - d->m_selectionMode = mode; +void GraphicsScene::setSelectionMode(GraphicsScene::SelectionMode mode) +{ + if (mode != d->m_selectionMode) { + d->m_selectionMode = mode; + emit this->selectionModeChanged(); + } } GraphicsSceneRedrawBlocker::GraphicsSceneRedrawBlocker(GraphicsScene* scene) diff --git a/src/graphics/graphics_scene.h b/src/graphics/graphics_scene.h index d57a4479..b448a500 100644 --- a/src/graphics/graphics_scene.h +++ b/src/graphics/graphics_scene.h @@ -29,10 +29,12 @@ class GraphicsScene : public QObject { opencascade::handle createV3dView(); const opencascade::handle& v3dViewer() const; - const opencascade::handle& defaultPrs3dDrawer() const; const opencascade::handle& mainSelector() const; bool hiddenLineDrawingOn() const; + const opencascade::handle& drawerDefault() const; + const opencascade::handle& drawerHighlight(Prs3d_TypeOfHighlight style) const; + void addObject(const GraphicsObjectPtr& object); void eraseObject(const GraphicsObjectPtr& object); @@ -44,6 +46,7 @@ class GraphicsScene : public QObject { void activateObjectSelection(const GraphicsObjectPtr& object, int mode); void deactivateObjectSelection(const GraphicsObjectPtr& object, int mode); + void deactivateObjectSelection(const GraphicsObjectPtr& object); void addSelectionFilter(const Handle_SelectMgr_Filter& filter); void removeSelectionFilter(const Handle_SelectMgr_Filter& filter); @@ -79,6 +82,9 @@ class GraphicsScene : public QObject { template void foreachDisplayedObject(FUNCTION fn) const; + template + void foreachActiveSelectionMode(const GraphicsObjectPtr& object, FUNCTION fn) const; + template void foreachOwner(const GraphicsObjectPtr& object, int selectionMode, FUNCTION fn) const; @@ -90,6 +96,7 @@ class GraphicsScene : public QObject { signals: void selectionChanged(); + void selectionModeChanged(); private: AIS_InteractiveContext* aisContextPtr() const; @@ -129,6 +136,15 @@ void GraphicsScene::foreachDisplayedObject(FUNCTION fn) const fn(ptr); } +template +void GraphicsScene::foreachActiveSelectionMode(const GraphicsObjectPtr& object, FUNCTION fn) const +{ + TColStd_ListOfInteger listMode; + this->aisContextPtr()->ActivatedModes(object, listMode); + for (GraphicsObjectSelectionMode mode : listMode) + fn(mode); +} + template void GraphicsScene::foreachOwner(const GraphicsObjectPtr& object, int selectionMode, FUNCTION fn) const { @@ -142,10 +158,8 @@ template void GraphicsScene::foreachSelectedOwner(FUNCTION fn) const { auto context = this->aisContextPtr(); - context->InitSelected(); - while (context->MoreSelected()) { + for (context->InitSelected(); context->MoreSelected(); context->NextSelected()) { fn(context->SelectedOwner()); - context->NextSelected(); } } @@ -153,15 +167,12 @@ template GraphicsOwnerPtr GraphicsScene::findSelectedOwner(PREDICATE fn) const { auto context = this->aisContextPtr(); - context->InitSelected(); - while (context->MoreSelected()) { + for (context->InitSelected(); context->MoreSelected(); context->NextSelected()) { if (fn(context->SelectedOwner())) return context->SelectedOwner(); - - context->NextSelected(); } - return GraphicsOwnerPtr(); + return {}; } } // namespace Mayo diff --git a/src/graphics/graphics_shape_object_driver.cpp b/src/graphics/graphics_shape_object_driver.cpp new file mode 100644 index 00000000..37c798fe --- /dev/null +++ b/src/graphics/graphics_shape_object_driver.cpp @@ -0,0 +1,144 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "graphics_shape_object_driver.h" + +#include "../base/caf_utils.h" +#include "../base/data_triangulation.h" +#include "../base/xcaf.h" +#include "graphics_utils.h" + +#include +#include +#include + +namespace Mayo { + +namespace { +struct GraphicsShapeObjectDriverI18N { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::GraphicsShapeObjectDriver) }; +} // namespace + +GraphicsShapeObjectDriver::GraphicsShapeObjectDriver() +{ + this->setDisplayModes({ + { DisplayMode_Wireframe, GraphicsShapeObjectDriverI18N::textId("Shape_Wireframe") }, + { DisplayMode_HiddenLineRemoval, GraphicsShapeObjectDriverI18N::textId("Shape_HiddenLineRemoval") }, + { DisplayMode_Shaded, GraphicsShapeObjectDriverI18N::textId("Shape_Shaded") }, + { DisplayMode_ShadedWithFaceBoundary, GraphicsShapeObjectDriverI18N::textId("Shape_ShadedWithFaceBoundary") } + }); + this->setDefaultDisplayMode(DisplayMode_ShadedWithFaceBoundary); +} + +GraphicsObjectDriver::Support GraphicsShapeObjectDriver::supportStatus(const TDF_Label& label) const +{ + if (XCaf::isShape(label)) + return Support::Complete; + + // TODO TNaming_Shape ? + // TDataXtd_Shape ? + + if (CafUtils::hasAttribute(label)) { + //return Support::Partial; + } + + return Support::None; +} + +GraphicsObjectPtr GraphicsShapeObjectDriver::createObject(const TDF_Label& label) const +{ + if (XCaf::isShape(label)) { + auto object = new XCAFPrs_AISObject(label); + object->SetDisplayMode(AIS_Shaded); + object->SetMaterial(Graphic3d_NOM_PLASTER); + object->Attributes()->SetFaceBoundaryDraw(true); + object->Attributes()->SetFaceBoundaryAspect( + new Prs3d_LineAspect(Quantity_NOC_BLACK, Aspect_TOL_SOLID, 1.)); + object->Attributes()->SetIsoOnTriangulation(true); + //object->Attributes()->SetShadingModel(Graphic3d_TypeOfShadingModel_Pbr, true/*overrideDefaults*/); + object->SetOwner(this); + return object; + } + else if (CafUtils::hasAttribute(label)) { + } + + return {}; +} + +void GraphicsShapeObjectDriver::applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const +{ + this->throwIf_differentDriver(object); + this->throwIf_invalidDisplayMode(mode); + + if (mode == this->currentDisplayMode(object)) + return; + + AIS_InteractiveContext* context = GraphicsUtils::AisObject_contextPtr(object); + if (!context) + return; + + auto fnSetViewComputedMode = [=](bool on) { + for (auto it = context->CurrentViewer()->DefinedViewIterator(); it.More(); it.Next()) + it.Value()->SetComputedMode(on); + }; + + if (mode == DisplayMode_HiddenLineRemoval) { + context->DefaultDrawer()->SetTypeOfHLR(Prs3d_TOH_PolyAlgo); + context->DefaultDrawer()->EnableDrawHiddenLine(); + fnSetViewComputedMode(true); + } + else { + context->DefaultDrawer()->SetTypeOfHLR(Prs3d_TOH_NotSet); + context->DefaultDrawer()->DisableDrawHiddenLine(); + fnSetViewComputedMode(false); + const AIS_DisplayMode aisDispMode = mode == DisplayMode_Wireframe ? AIS_WireFrame : AIS_Shaded; + const bool showFaceBounds = mode == DisplayMode_ShadedWithFaceBoundary; + if (object->DisplayMode() != aisDispMode) + context->SetDisplayMode(object, aisDispMode, false); + + if (object->Attributes()->FaceBoundaryDraw() != showFaceBounds) { + object->Attributes()->SetFaceBoundaryDraw(showFaceBounds); + auto aisLink = Handle_AIS_ConnectedInteractive::DownCast(object); + if (aisLink && aisLink->HasConnection()) { + aisLink->ConnectedTo()->Attributes()->SetFaceBoundaryDraw(showFaceBounds); + aisLink->ConnectedTo()->Redisplay(true); + } + else { + object->Redisplay(true); + } + } + } + + // context->UpdateCurrentViewer(); +} + +Enumeration::Value GraphicsShapeObjectDriver::currentDisplayMode(const GraphicsObjectPtr& object) const +{ + this->throwIf_differentDriver(object); + if (GraphicsUtils::AisObject_contextPtr(object)->DrawHiddenLine()) + return DisplayMode_HiddenLineRemoval; + + const int displayMode = object->DisplayMode(); + if (displayMode == AIS_WireFrame) + return DisplayMode_Wireframe; + + if (displayMode == AIS_Shaded) { + return object->Attributes()->FaceBoundaryDraw() ? + DisplayMode_ShadedWithFaceBoundary : + DisplayMode_Shaded; + } + + return -1; +} + +std::unique_ptr +GraphicsShapeObjectDriver::properties(Span spanObject) const +{ + this->throwIf_differentDriver(spanObject); + //return std::make_unique(spanObject); + return {}; +} + +} // namespace Mayo diff --git a/src/graphics/graphics_shape_object_driver.h b/src/graphics/graphics_shape_object_driver.h new file mode 100644 index 00000000..ef4ad2c2 --- /dev/null +++ b/src/graphics/graphics_shape_object_driver.h @@ -0,0 +1,38 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "graphics_object_driver.h" + +namespace Mayo { + +class GraphicsShapeObjectDriver; +DEFINE_STANDARD_HANDLE(GraphicsShapeObjectDriver, GraphicsObjectDriver) +using GraphicsShapeObjectDriverPtr = Handle(GraphicsShapeObjectDriver); + +// Provides creation and configuration of graphics objects for BRep shapes +class GraphicsShapeObjectDriver : public GraphicsObjectDriver { +public: + GraphicsShapeObjectDriver(); + + Support supportStatus(const TDF_Label& label) const override; + GraphicsObjectPtr createObject(const TDF_Label& label) const override; + void applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const override; + Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const override; + std::unique_ptr properties(Span spanObject) const override; + + enum DisplayMode { + DisplayMode_Wireframe, + DisplayMode_HiddenLineRemoval, + DisplayMode_Shaded, + DisplayMode_ShadedWithFaceBoundary + }; + + DEFINE_STANDARD_RTTI_INLINE(GraphicsShapeObjectDriver, GraphicsObjectDriver) +}; + +} // namespace Mayo diff --git a/src/graphics/graphics_tree_node_mapping.cpp b/src/graphics/graphics_tree_node_mapping.cpp deleted file mode 100644 index 220a9bf3..00000000 --- a/src/graphics/graphics_tree_node_mapping.cpp +++ /dev/null @@ -1,67 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "graphics_tree_node_mapping.h" - -#include "../base/brep_utils.h" -#include "../base/document.h" -#include "../base/document_tree_node.h" - -#include -#include -#include - -namespace Mayo { - -GraphicsShapeTreeNodeMapping::GraphicsShapeTreeNodeMapping(TopAbs_ShapeEnum shapeType) - : m_shapeType(shapeType) -{ -} - -int GraphicsShapeTreeNodeMapping::selectionMode() const -{ - return AIS_Shape::SelectionMode(m_shapeType); -} - -std::vector -GraphicsShapeTreeNodeMapping::findGraphicsOwners(const DocumentTreeNode& treeNode) const -{ - const TopLoc_Location shapeLoc = treeNode.document()->xcaf().shapeAbsoluteLocation(treeNode.id()); - const TopoDS_Shape shape = XCaf::shape(treeNode.label()).Located(shapeLoc); - std::vector vecShape; - if (BRepUtils::moreComplex(shape.ShapeType(), m_shapeType)) { - BRepUtils::forEachSubShape(shape, m_shapeType, [&](const TopoDS_Shape& subShape) { - vecShape.push_back(subShape); - }); - } - else if (shape.ShapeType() == m_shapeType) { - vecShape.push_back(shape); - } - - std::vector vecGfxOwner; - for (const TopoDS_Shape& shape : vecShape) { - auto it = m_mapGfxOwner.find(BRepUtils::hashCode(shape)); - if (it != m_mapGfxOwner.cend()) - vecGfxOwner.push_back(it->second); - } - - return vecGfxOwner; -} - -bool GraphicsShapeTreeNodeMapping::mapGraphicsOwner(const GraphicsOwnerPtr& gfxOwner) -{ - auto brepOwner = Handle_StdSelect_BRepOwner::DownCast(gfxOwner); - if (brepOwner.IsNull()) - return false; - - if (brepOwner->Shape().ShapeType() != m_shapeType) - return false; - - auto result = m_mapGfxOwner.emplace(BRepUtils::hashCode(brepOwner->Shape()), brepOwner); - return result.second; -} - -} // namespace Mayo diff --git a/src/graphics/graphics_tree_node_mapping.h b/src/graphics/graphics_tree_node_mapping.h deleted file mode 100644 index 3db8aabb..00000000 --- a/src/graphics/graphics_tree_node_mapping.h +++ /dev/null @@ -1,39 +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 "graphics_owner_ptr.h" -#include -#include -#include - -namespace Mayo { - -class DocumentTreeNode; - -class GraphicsTreeNodeMapping { -public: - virtual ~GraphicsTreeNodeMapping() = default; - virtual int selectionMode() const = 0; - virtual std::vector findGraphicsOwners(const DocumentTreeNode& treeNode) const = 0; - virtual bool mapGraphicsOwner(const GraphicsOwnerPtr& gfxOwner) = 0; -}; - -class GraphicsShapeTreeNodeMapping : public GraphicsTreeNodeMapping { -public: - GraphicsShapeTreeNodeMapping(TopAbs_ShapeEnum shapeType); - - int selectionMode() const override; - std::vector findGraphicsOwners(const DocumentTreeNode& treeNode) const override; - bool mapGraphicsOwner(const GraphicsOwnerPtr& gfxOwner) override; - -private: - std::unordered_map m_mapGfxOwner; - TopAbs_ShapeEnum m_shapeType; -}; - -} // namespace Mayo diff --git a/src/graphics/graphics_tree_node_mapping_driver.cpp b/src/graphics/graphics_tree_node_mapping_driver.cpp deleted file mode 100644 index 9671856e..00000000 --- a/src/graphics/graphics_tree_node_mapping_driver.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "graphics_tree_node_mapping_driver.h" - -#include "../base/document.h" - -namespace Mayo { - -std::unique_ptr -GraphicsShapeTreeNodeMappingDriver::createMapping(const DocumentTreeNode& entityTreeNode) const -{ - if (!entityTreeNode.isEntity()) - return {}; - - if (!entityTreeNode.isValid()) - return {}; - - if (!XCaf::isShape(entityTreeNode.label())) - return {}; - - int solidCount = 0; - int faceCount = 0; - const DocumentPtr doc = entityTreeNode.document(); - traverseTree(entityTreeNode.id(), doc->modelTree(), [&](TreeNodeId treeNodeId) { - const TopoDS_Shape shape = XCaf::shape(doc->modelTree().nodeData(treeNodeId)); - if (shape.ShapeType() == TopAbs_SOLID) - ++solidCount; - else if (shape.ShapeType() == TopAbs_FACE) - ++faceCount; - }); - - const TopAbs_ShapeEnum shapeType = solidCount > faceCount ? TopAbs_SOLID : TopAbs_FACE; - return std::make_unique(shapeType); -} - -} // namespace Mayo diff --git a/src/graphics/graphics_tree_node_mapping_driver.h b/src/graphics/graphics_tree_node_mapping_driver.h deleted file mode 100644 index 3e9e65a9..00000000 --- a/src/graphics/graphics_tree_node_mapping_driver.h +++ /dev/null @@ -1,25 +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 "graphics_tree_node_mapping.h" - -namespace Mayo { - -class GraphicsTreeNodeMappingDriver { -public: - using MappingPtr = std::unique_ptr; - virtual ~GraphicsTreeNodeMappingDriver() = default; - virtual MappingPtr createMapping(const DocumentTreeNode& entityTreeNode) const = 0; -}; - -class GraphicsShapeTreeNodeMappingDriver : public GraphicsTreeNodeMappingDriver { -public: - MappingPtr createMapping(const DocumentTreeNode& entityTreeNode) const override; -}; - -} // namespace Mayo diff --git a/src/graphics/graphics_tree_node_mapping_driver_table.cpp b/src/graphics/graphics_tree_node_mapping_driver_table.cpp deleted file mode 100644 index 6796a6a5..00000000 --- a/src/graphics/graphics_tree_node_mapping_driver_table.cpp +++ /dev/null @@ -1,28 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "graphics_tree_node_mapping_driver_table.h" - -namespace Mayo { - -void GraphicsTreeNodeMappingDriverTable::addDriver(DriverPtr driver) -{ - m_vecDriver.push_back(std::move(driver)); -} - -std::unique_ptr -GraphicsTreeNodeMappingDriverTable::createMapping(const DocumentTreeNode& entityTreeNode) const -{ - for (const DriverPtr& driver : m_vecDriver) { - std::unique_ptr ptr = driver->createMapping(entityTreeNode); - if (ptr) - return ptr; - } - - return {}; -} - -} // namespace Mayo diff --git a/src/graphics/graphics_tree_node_mapping_driver_table.h b/src/graphics/graphics_tree_node_mapping_driver_table.h deleted file mode 100644 index 3e00f0ab..00000000 --- a/src/graphics/graphics_tree_node_mapping_driver_table.h +++ /dev/null @@ -1,29 +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 "graphics_tree_node_mapping_driver.h" -#include "../base/span.h" -#include -#include - -namespace Mayo { - -class GraphicsTreeNodeMappingDriverTable { -public: - using DriverPtr = std::unique_ptr; - - void addDriver(DriverPtr driver); - Span drivers() const { return m_vecDriver; } - - std::unique_ptr createMapping(const DocumentTreeNode& entityTreeNode) const; - -private: - std::vector m_vecDriver; -}; - -} // namespace Mayo diff --git a/src/graphics/graphics_utils.cpp b/src/graphics/graphics_utils.cpp index 616f5ce5..58eb565d 100644 --- a/src/graphics/graphics_utils.cpp +++ b/src/graphics/graphics_utils.cpp @@ -169,7 +169,7 @@ int GraphicsUtils::AspectWindow_height(const Handle_Aspect_Window& wnd) return h; } -void GraphicsUtils::Gpx3dClipPlane_setCappingHatch( +void GraphicsUtils::Gfx3dClipPlane_setCappingHatch( const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch) { if (hatch == Aspect_HS_SOLID) @@ -180,14 +180,14 @@ void GraphicsUtils::Gpx3dClipPlane_setCappingHatch( plane->SetCappingHatch(hatch); } -void GraphicsUtils::Gpx3dClipPlane_setNormal(const Handle_Graphic3d_ClipPlane& plane, const gp_Dir& n) +void GraphicsUtils::Gfx3dClipPlane_setNormal(const Handle_Graphic3d_ClipPlane& plane, const gp_Dir& n) { const double planePos = MathUtils::planePosition(plane->ToPlane()); const gp_Vec placement(planePos * gp_Vec(n)); plane->SetEquation(gp_Pln(placement.XYZ(), n)); } -void GraphicsUtils::Gpx3dClipPlane_setPosition(const Handle_Graphic3d_ClipPlane& plane, double pos) +void GraphicsUtils::Gfx3dClipPlane_setPosition(const Handle_Graphic3d_ClipPlane& plane, double pos) { const gp_Dir& n = plane->ToPlane().Axis().Direction(); if (MathUtils::isReversedStandardDir(n)) diff --git a/src/graphics/graphics_utils.h b/src/graphics/graphics_utils.h index 845d2bdc..b89ee67a 100644 --- a/src/graphics/graphics_utils.h +++ b/src/graphics/graphics_utils.h @@ -17,11 +17,8 @@ namespace Mayo { struct GraphicsUtils { 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 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 void AisContext_eraseObject( const Handle_AIS_InteractiveContext& context, @@ -39,11 +36,11 @@ struct GraphicsUtils { static int AspectWindow_width(const Handle_Aspect_Window& wnd); static int AspectWindow_height(const Handle_Aspect_Window& wnd); - static void Gpx3dClipPlane_setCappingHatch( + static void Gfx3dClipPlane_setCappingHatch( const Handle_Graphic3d_ClipPlane& plane, Aspect_HatchStyle hatch); - static void Gpx3dClipPlane_setNormal( + static void Gfx3dClipPlane_setNormal( const Handle_Graphic3d_ClipPlane& plane, const gp_Dir& n); - static void Gpx3dClipPlane_setPosition( + static void Gfx3dClipPlane_setPosition( const Handle_Graphic3d_ClipPlane& plane, double pos); static bool ImagePixmap_flipY(Image_PixMap& pixmap); diff --git a/src/graphics/v3d_view_controller.cpp b/src/graphics/v3d_view_controller.cpp index 0c5ef416..a34d18ea 100644 --- a/src/graphics/v3d_view_controller.cpp +++ b/src/graphics/v3d_view_controller.cpp @@ -18,11 +18,6 @@ V3dViewController::V3dViewController(const Handle_V3d_View& view, QObject* paren { } -V3dViewController::~V3dViewController() -{ - delete m_rubberBand; -} - void V3dViewController::zoomIn() { m_view->SetScale(m_view->Scale() * 1.1); // +10% @@ -67,6 +62,11 @@ bool V3dViewController::isPanningStarted() const return m_dynamicAction == DynamicAction::Panning; } +bool V3dViewController::isZoomStarted() const +{ + return m_dynamicAction == DynamicAction::Zoom; +} + bool V3dViewController::isWindowZoomingStarted() const { return m_dynamicAction == DynamicAction::WindowZoom; @@ -74,6 +74,9 @@ bool V3dViewController::isWindowZoomingStarted() const void V3dViewController::rotation(const QPoint& currPos) { + if (this->currentDynamicAction() != DynamicAction::Rotation) + this->stopDynamicAction(); + if (!this->isRotationStarted()) { this->startDynamicAction(DynamicAction::Rotation); m_view->StartRotation(currPos.x(), currPos.y()); @@ -86,6 +89,9 @@ void V3dViewController::rotation(const QPoint& currPos) void V3dViewController::pan(const QPoint& prevPos, const QPoint& currPos) { + if (this->currentDynamicAction() != DynamicAction::Panning) + this->stopDynamicAction(); + if (!this->isPanningStarted()) this->startDynamicAction(DynamicAction::Panning); @@ -93,6 +99,21 @@ void V3dViewController::pan(const QPoint& prevPos, const QPoint& currPos) this->redrawView(); } +void V3dViewController::zoom(const QPoint& prevPos, const QPoint& currPos) +{ + if (this->currentDynamicAction() != DynamicAction::Zoom) + this->stopDynamicAction(); + + if (!this->isZoomStarted()) { + this->startDynamicAction(DynamicAction::Zoom); + m_view->StartZoomAtPoint(currPos.x(), currPos.y()); + } + else { + m_view->Zoom(-prevPos.y(), 0, -currPos.y(), 0); // Zoom by vertical movement + this->redrawView(); + } +} + void V3dViewController::windowFitAll(const QPoint& posMin, const QPoint& posMax) { if (std::abs(posMin.x() - posMax.x()) > 1 || std::abs(posMin.y() - posMax.y()) > 1) diff --git a/src/graphics/v3d_view_controller.h b/src/graphics/v3d_view_controller.h index ca6b3ffe..fd984abb 100644 --- a/src/graphics/v3d_view_controller.h +++ b/src/graphics/v3d_view_controller.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace Mayo { @@ -20,6 +21,7 @@ class V3dViewController : public QObject { None, Panning, Rotation, + Zoom, WindowZoom, InstantZoom }; @@ -31,7 +33,7 @@ class V3dViewController : public QObject { }; V3dViewController(const Handle_V3d_View& view, QObject* parent = nullptr); - virtual ~V3dViewController(); + virtual ~V3dViewController() = default; DynamicAction currentDynamicAction() const; bool hasCurrentDynamicAction() const; @@ -56,10 +58,12 @@ class V3dViewController : public QObject { bool isRotationStarted() const; bool isPanningStarted() const; + bool isZoomStarted() const; bool isWindowZoomingStarted() const; void rotation(const QPoint& currPos); void pan(const QPoint& prevPos, const QPoint& currPos); + void zoom(const QPoint& prevPos, const QPoint& currPos); void windowFitAll(const QPoint& posMin, const QPoint& posMax); @@ -69,7 +73,7 @@ class V3dViewController : public QObject { void startInstantZoom(const QPoint& currPos); void stopInstantZoom(); - virtual AbstractRubberBand* createRubberBand() = 0; + virtual std::unique_ptr createRubberBand() = 0; void drawRubberBand(const QPoint& posMin, const QPoint& posMax); void hideRubberBand(); @@ -81,7 +85,7 @@ class V3dViewController : public QObject { private: Handle_V3d_View m_view; DynamicAction m_dynamicAction = DynamicAction::None; - AbstractRubberBand* m_rubberBand = nullptr; + std::unique_ptr m_rubberBand; double m_instantZoomFactor = 5.; Handle_Graphic3d_Camera m_cameraBackup; QPoint m_posRubberBandStart; diff --git a/src/gui/gui_application.cpp b/src/gui/gui_application.cpp index 52fd8ae1..467d1eea 100644 --- a/src/gui/gui_application.cpp +++ b/src/gui/gui_application.cpp @@ -18,9 +18,7 @@ namespace Mayo { GuiApplication::GuiApplication(const ApplicationPtr& app) : QObject(app.get()), m_app(app), - m_selectionModel(new ApplicationItemSelectionModel(this)), - m_gfxObjectDriverTable(new GraphicsObjectDriverTable), - m_gfxTreeNodeMappingDriverTable(new GraphicsTreeNodeMappingDriverTable) + m_selectionModel(new ApplicationItemSelectionModel(this)) { QObject::connect( app.get(), &Application::documentAdded, @@ -52,14 +50,32 @@ ApplicationItemSelectionModel* GuiApplication::selectionModel() const return m_selectionModel; } -GraphicsObjectDriverTable* GuiApplication::graphicsObjectDriverTable() const +void GuiApplication::addGraphicsObjectDriver(GraphicsObjectDriverPtr ptr) { - return m_gfxObjectDriverTable.get(); + m_vecGfxObjectDriver.push_back(ptr); } -GraphicsTreeNodeMappingDriverTable* GuiApplication::graphicsTreeNodeMappingDriverTable() const +void GuiApplication::addGraphicsObjectDriver(std::unique_ptr ptr) { - return m_gfxTreeNodeMappingDriverTable.get(); + m_vecGfxObjectDriver.push_back(ptr.release()); // Will be converted to opencascade::handle<> +} + +GraphicsObjectPtr GuiApplication::createGraphicsObject(const TDF_Label& label) const +{ + GraphicsObjectDriver* driverPartialSupport = nullptr; + for (const GraphicsObjectDriverPtr& driver : m_vecGfxObjectDriver) { + const GraphicsObjectDriver::Support support = driver->supportStatus(label); + if (support == GraphicsObjectDriver::Support::Complete) + return driver->createObject(label); + + if (support == GraphicsObjectDriver::Support::Partial) + driverPartialSupport = driver.get(); + } + + if (driverPartialSupport) + return driverPartialSupport->createObject(label); + else + return {}; } void GuiApplication::onDocumentAdded(const DocumentPtr& doc) diff --git a/src/gui/gui_application.h b/src/gui/gui_application.h index aea972c8..3dd39c7b 100644 --- a/src/gui/gui_application.h +++ b/src/gui/gui_application.h @@ -9,8 +9,7 @@ #include "../base/application_ptr.h" #include "../base/application_item_selection_model.h" #include "../base/span.h" -#include "../graphics/graphics_object_driver_table.h" -#include "../graphics/graphics_tree_node_mapping_driver_table.h" +#include "../graphics/graphics_object_driver.h" #include "gui_document.h" #include @@ -34,8 +33,10 @@ class GuiApplication : public QObject { ApplicationItemSelectionModel* selectionModel() const; - GraphicsObjectDriverTable* graphicsObjectDriverTable() const; - GraphicsTreeNodeMappingDriverTable* graphicsTreeNodeMappingDriverTable() const; + void addGraphicsObjectDriver(GraphicsObjectDriverPtr ptr); + void addGraphicsObjectDriver(std::unique_ptr ptr); + Span graphicsObjectDrivers() const { return m_vecGfxObjectDriver; } + GraphicsObjectPtr createGraphicsObject(const TDF_Label& label) const; // Whether a GuiDocument object is automatically created once a Document is added in Application bool automaticDocumentMapping() const { return m_automaticDocumentMapping; } @@ -59,8 +60,7 @@ class GuiApplication : public QObject { ApplicationPtr m_app; std::vector m_vecGuiDocument; ApplicationItemSelectionModel* m_selectionModel = nullptr; - std::unique_ptr m_gfxObjectDriverTable; - std::unique_ptr m_gfxTreeNodeMappingDriverTable; + std::vector m_vecGfxObjectDriver; QMetaObject::Connection m_connApplicationItemSelectionChanged; bool m_automaticDocumentMapping = true; }; diff --git a/src/gui/gui_document.cpp b/src/gui/gui_document.cpp index 22ea50d8..4c779766 100644 --- a/src/gui/gui_document.cpp +++ b/src/gui/gui_document.cpp @@ -13,7 +13,6 @@ #include "../base/document.h" #include "../base/tkernel_utils.h" #include "../gui/gui_application.h" -#include "../graphics/graphics_object_driver_table.h" #include "../graphics/graphics_utils.h" #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) @@ -29,9 +28,6 @@ namespace Mayo { namespace Internal { -// Defined in gui_create_gfx_driver.cpp -Handle_Graphic3d_GraphicDriver createGfxDriver(); - static Handle_AIS_Trihedron createOriginTrihedron() { Handle_Geom_Axis2Placement axis = new Geom_Axis2Placement(gp::XOY()); @@ -85,7 +81,7 @@ GuiDocument::GuiDocument(const DocumentPtr& doc, GuiApplication* guiApp) this->setViewTrihedronCorner(Qt::BottomLeftCorner); #endif - //m_v3dView->SetShadingModel(V3d_PHONG); + //m_v3dView->SetShadingModel(Graphic3d_TypeOfShadingModel_Pbr); // 3D view - Enable anti-aliasing with MSAA m_v3dView->ChangeRenderingParams().IsAntialiasingEnabled = true; m_v3dView->ChangeRenderingParams().NbMsaaSamples = 4; @@ -462,6 +458,15 @@ int GuiDocument::aisViewCubeBoundingSize() const #endif } +bool GuiDocument::isAisViewCubeObject([[maybe_unused]] const GraphicsObjectPtr& gfxObject) +{ +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) + return !opencascade::handle::DownCast(gfxObject).IsNull(); +#else + return false; +#endif +} + const GuiDocument::GradientBackground& GuiDocument::defaultGradientBackground() { return Internal::defaultGradientBackground(); @@ -541,7 +546,7 @@ void GuiDocument::mapEntity(TreeNodeId entityTreeNodeId) if (docModelTree.nodeIsLeaf(id)) { GraphicsObjectPtr gfxProduct = CppUtils::findValue(nodeLabel, mapLabelGfxProduct); if (!gfxProduct) { - gfxProduct = m_guiApp->graphicsObjectDriverTable()->createObject(nodeLabel); + gfxProduct = m_guiApp->createGraphicsObject(nodeLabel); if (!gfxProduct) return; diff --git a/src/gui/gui_document.h b/src/gui/gui_document.h index eb57c39a..000a87af 100644 --- a/src/gui/gui_document.h +++ b/src/gui/gui_document.h @@ -7,6 +7,7 @@ #pragma once #include "../base/document.h" +#include "../base/global.h" #include "../base/tkernel_utils.h" #include "../graphics/graphics_object_driver.h" #include "../graphics/graphics_scene.h" @@ -87,6 +88,7 @@ class GuiDocument : public QObject { void setViewTrihedronCorner(Qt::Corner corner); int aisViewCubeBoundingSize() const; + static bool isAisViewCubeObject(const GraphicsObjectPtr& gfxObject); // -- Background struct GradientBackground { diff --git a/src/io_dxf/dxf.cpp b/src/io_dxf/dxf.cpp index 7419e709..048a38fa 100644 --- a/src/io_dxf/dxf.cpp +++ b/src/io_dxf/dxf.cpp @@ -432,7 +432,7 @@ void CDxfWrite::makeBlockSectionHead(void) } } -std::string CDxfWrite::getPlateFile(std::string fileSpec) +std::string CDxfWrite::getPlateFile(const std::string& fileSpec) { std::stringstream outString; const std::filesystem::path fpath(fileSpec); @@ -519,9 +519,9 @@ 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" << endl; (*outStream) << "LINE" << endl; @@ -951,10 +951,10 @@ void CDxfWrite::writeText(const char* text, const double* location1, const doubl //*************************** //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, +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) + std::ostringstream* outStream, const std::string& handle, + const std::string& ownerHandle) { (void) location2; @@ -1020,9 +1020,9 @@ void CDxfWrite::putText(const char* text, const Base::Vector3d location1, const } -void CDxfWrite::putArrow(Base::Vector3d arrowPos, Base::Vector3d barb1Pos, Base::Vector3d barb2Pos, - std::ostringstream* outStream, const std::string handle, - const std::string ownerHandle) +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" << endl; (*outStream) << "SOLID" << endl; diff --git a/src/io_dxf/dxf.h b/src/io_dxf/dxf.h index 16863e98..76f50317 100644 --- a/src/io_dxf/dxf.h +++ b/src/io_dxf/dxf.h @@ -134,20 +134,20 @@ class ImportExport CDxfWrite{ std::ostringstream* m_ssLayer; protected: - 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, + 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, - const std::string ownerHandle); - void putArrow(Base::Vector3d arrowPos, Base::Vector3d barb1Pos, Base::Vector3d barb2Pos, - std::ostringstream* outStream, const std::string handle, - const std::string ownerHandle); + 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, + const std::string& ownerHandle); //! copy boiler plate file - std::string getPlateFile(std::string fileSpec); - void setDataDir(std::string s) { m_dataDir = s; } + 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); diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index 1b7f33a0..8f40e6b3 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -130,7 +130,7 @@ class DxfReader::Properties : public PropertyGroup { PropertyDouble scaling{ this, textId("scaling") }; PropertyBool importAnnotations{ this, textId("importAnnotations") }; PropertyBool groupLayers{ this, textId("groupLayers") }; - PropertyEnumeration fontNameForTextObjects{ this, textId("fontNameForTextObjects"), systemFontNames() }; + PropertyEnumeration fontNameForTextObjects{ this, textId("fontNameForTextObjects"), &systemFontNames() }; }; bool DxfReader::readFile(const FilePath& filepath, TaskProgress* progress) @@ -138,7 +138,7 @@ bool DxfReader::readFile(const FilePath& filepath, TaskProgress* progress) m_layers.clear(); DxfReader::Internal internalReader(filepath, progress); internalReader.setParameters(m_params); - internalReader.setMessenger(this->messenger() ? this->messenger() : NullMessenger::instance()); + internalReader.setMessenger(this->messenger() ? this->messenger() : &Messenger::null()); internalReader.DoRead(); m_layers = std::move(internalReader.layers()); return !internalReader.Failed(); @@ -169,7 +169,7 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) if (it != mapAciColorLabel.cend()) return it->second; - if (0 <= aci && aci < std::size(aciTable)) { + 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)); @@ -184,7 +184,7 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) int shapeCount = 0; for (const auto& [layerName, vecEntity] : m_layers) { if (!startsWith(layerName, "BLOCKS")) { - shapeCount += vecEntity.size(); + shapeCount = CppUtils::safeStaticCast(shapeCount + vecEntity.size()); const TDF_Label layerLabel = layerTool->AddLayer(to_OccExtString(layerName)); mapLayerNameLabel.insert({ layerName, layerLabel }); } @@ -245,7 +245,7 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) } } - iShape += vecEntity.size(); + iShape = CppUtils::safeStaticCast(iShape + vecEntity.size()); fnUpdateProgressValue(); } } @@ -527,7 +527,7 @@ Handle_Geom_BSplineCurve DxfReader::Internal::createSplineFromPolesAndKnots(stru || sd.controlz.size() > numPoles || sd.weight.size() > numPoles) { - return nullptr; + return {}; } // handle the poles @@ -553,7 +553,7 @@ Handle_Geom_BSplineCurve DxfReader::Internal::createSplineFromPolesAndKnots(stru TColStd_Array1OfReal occknots(1, numKnots); index = 1; for (auto k : unique) { - const size_t m = std::count(sd.knot.begin(), sd.knot.end(), k); + const auto m = CppUtils::safeStaticCast(std::count(sd.knot.begin(), sd.knot.end(), k)); occknots(index) = k; occmults(index) = m; index++; @@ -581,7 +581,7 @@ Handle_Geom_BSplineCurve DxfReader::Internal::createInterpolationSpline(struct S { const size_t numPoints = sd.fit_points; if (sd.fitx.size() > numPoints || sd.fity.size() > numPoints || sd.fitz.size() > numPoints) - return nullptr; + return {}; // handle the poles Handle_TColgp_HArray1OfPnt fitpoints = new TColgp_HArray1OfPnt(1, sd.fit_points); diff --git a/src/io_gmio/io_gmio_amf_writer.cpp b/src/io_gmio/io_gmio_amf_writer.cpp index e5d1f791..d2826241 100644 --- a/src/io_gmio/io_gmio_amf_writer.cpp +++ b/src/io_gmio/io_gmio_amf_writer.cpp @@ -9,6 +9,8 @@ #include "../base/application_item.h" #include "../base/brep_utils.h" #include "../base/caf_utils.h" +#include "../base/cpp_utils.h" +#include "../base/data_triangulation.h" #include "../base/math_utils.h" #include "../base/meta_enum.h" #include "../base/property_builtins.h" @@ -20,7 +22,6 @@ #include #include -#include #include #include @@ -271,10 +272,11 @@ bool GmioAmfWriter::writeFile(const FilePath& filepath, TaskProgress* progress) auto fnAmfFloat64Format = [](FloatTextFormat format) { switch (format) { - case FloatTextFormat::Decimal: return GMIO_FLOAT_TEXT_FORMAT_DECIMAL_UPPERCASE; + case FloatTextFormat::Decimal: return GMIO_FLOAT_TEXT_FORMAT_DECIMAL_UPPERCASE; case FloatTextFormat::Scientific: return GMIO_FLOAT_TEXT_FORMAT_SCIENTIFIC_UPPERCASE; - case FloatTextFormat::Shortest: return GMIO_FLOAT_TEXT_FORMAT_SHORTEST_UPPERCASE; + case FloatTextFormat::Shortest: return GMIO_FLOAT_TEXT_FORMAT_SHORTEST_UPPERCASE; } + return GMIO_FLOAT_TEXT_FORMAT_DECIMAL_UPPERCASE; }; gmio_amf_write_options amfOptions = {}; @@ -286,7 +288,7 @@ bool GmioAmfWriter::writeFile(const FilePath& filepath, TaskProgress* progress) amfOptions.create_zip_archive = m_params.createZipArchive; amfOptions.dont_use_zip64_extensions = !m_params.useZip64; amfOptions.zip_entry_filename = m_params.zipEntryFilename.c_str(); - amfOptions.zip_entry_filename_len = m_params.zipEntryFilename.size(); + amfOptions.zip_entry_filename_len = CppUtils::safeStaticCast(m_params.zipEntryFilename.size()); // TODO Handle gmio_amf_write_options::z_compress_options const int error = gmio_amf_write_file(filepath.u8string().c_str(), &amfDoc, &amfOptions); return gmio_no_error(error); @@ -336,7 +338,7 @@ int GmioAmfWriter::createObject(const TDF_Label& labelShape) } // -- Triangulation ? - auto attrPolyTri = CafUtils::findAttribute(labelShape); + auto attrPolyTri = CafUtils::findAttribute(labelShape); if (!attrPolyTri.IsNull()) { fnAddMesh(attrPolyTri->Get(), TopLoc_Location()); } @@ -357,7 +359,7 @@ int GmioAmfWriter::createObject(const TDF_Label& labelShape) materialId = itColor - m_vecMaterial.cbegin(); } else { - materialId = m_vecMaterial.size(); + materialId = CppUtils::safeStaticCast(m_vecMaterial.size()); Material material; material.id = materialId; material.color = color; @@ -368,9 +370,9 @@ int GmioAmfWriter::createObject(const TDF_Label& labelShape) // Add object Object object; - object.id = m_vecObject.size(); + object.id = CppUtils::safeStaticCast(m_vecObject.size()); object.firstMeshId = meshCount; - object.lastMeshId = m_vecMesh.size() - 1; + object.lastMeshId = CppUtils::safeStaticCast(m_vecMesh.size()) - 1; object.name = to_stdString(CafUtils::labelAttrStdName(labelShape)); object.materialId = materialId; m_vecObject.push_back(std::move(object)); diff --git a/src/io_image/io_image.cpp b/src/io_image/io_image.cpp index b7293e9c..552bd6a3 100644 --- a/src/io_image/io_image.cpp +++ b/src/io_image/io_image.cpp @@ -8,8 +8,10 @@ #include "../base/application_item.h" #include "../base/caf_utils.h" +#include "../base/cpp_utils.h" #include "../base/document.h" #include "../base/filepath_conv.h" +#include "../base/io_system.h" #include "../base/math_utils.h" #include "../base/messenger.h" #include "../base/occ_progress_indicator.h" @@ -90,23 +92,9 @@ ImageWriter::ImageWriter(GuiApplication* guiApp) bool ImageWriter::transfer(Span appItems, TaskProgress* /*progress*/) { - m_setDoc.clear(); - m_setNode.clear(); - - for (const ApplicationItem& item : appItems) { - if (item.isDocument()) - m_setDoc.insert(item.document()); - } - - for (const ApplicationItem& item : appItems) { - if (item.isDocumentTreeNode()) { - auto itDoc = m_setDoc.find(item.document()); - if (itDoc == m_setDoc.cend()) - m_setNode.insert(item.documentTreeNode().label()); - } - } - - if (m_setDoc.empty() && m_setNode.empty()) + m_vecAppItem.clear(); + System::visitUniqueItems(appItems, [&](const ApplicationItem& item) { m_vecAppItem.push_back(item); }); + if (m_vecAppItem.empty()) this->messenger()->emitWarning(ImageWriterI18N::textIdTr("No transferred application items")); return true; @@ -121,24 +109,24 @@ bool ImageWriter::writeFile(const FilePath& filepath, TaskProgress* progress) GraphicsScene gfxScene; Handle_V3d_View view = ImageWriter::createV3dView(&gfxScene, m_params); - int itemProgress = 0; - const int itemCount = m_setDoc.size() + m_setNode.size(); - // Render documents(iterate other root entities) - for (const DocumentPtr& doc : m_setDoc) { - for (int i = 0; i < doc->entityCount(); ++i) { - const TDF_Label labelEntity = doc->entityLabel(i); - GraphicsObjectPtr gfxObject = m_guiApp->graphicsObjectDriverTable()->createObject(labelEntity); - gfxScene.addObject(gfxObject); + const int itemCount = CppUtils::safeStaticCast(m_vecAppItem.size()); + // Render application items + for (const ApplicationItem& appItem : m_vecAppItem) { + if (appItem.isDocument()) { + // Iterate other root entities + const DocumentPtr doc = appItem.document(); + for (int i = 0; i < doc->entityCount(); ++i) { + const TDF_Label labelEntity = doc->entityLabel(i); + gfxScene.addObject(m_guiApp->createGraphicsObject(labelEntity)); + } + } + else if (appItem.isDocumentTreeNode()) { + const TDF_Label labelNode = appItem.documentTreeNode().label(); + gfxScene.addObject(m_guiApp->createGraphicsObject(labelNode)); } - progress->setValue(MathUtils::mappedValue(++itemProgress, 0, itemCount, 0, 100)); - } - - // Render document tree nodes - for (const TDF_Label& labelNode : m_setNode) { - GraphicsObjectPtr gfxObject = m_guiApp->graphicsObjectDriverTable()->createObject(labelNode); - gfxScene.addObject(gfxObject); - progress->setValue(MathUtils::mappedValue(++itemProgress, 0, itemCount, 0, 100)); + const auto itemProgress = &appItem - &m_vecAppItem.front(); + progress->setValue(MathUtils::mappedValue(itemProgress, 0, itemCount, 0, 100)); } gfxScene.redraw(); diff --git a/src/io_image/io_image.h b/src/io_image/io_image.h index c7c2dee4..80770999 100644 --- a/src/io_image/io_image.h +++ b/src/io_image/io_image.h @@ -16,7 +16,8 @@ #include #include #include -#include + +#include // Pre-decls namespace Mayo { @@ -62,8 +63,7 @@ class ImageWriter : public Writer { class Properties; GuiApplication* m_guiApp = nullptr; Parameters m_params; - std::unordered_set m_setDoc; - std::unordered_set m_setNode; + std::vector m_vecAppItem; }; class ImageFactoryWriter : public FactoryWriter { diff --git a/src/io_occ/io_occ_brep.h b/src/io_occ/io_occ_brep.h index 58771795..eb3c4199 100644 --- a/src/io_occ/io_occ_brep.h +++ b/src/io_occ/io_occ_brep.h @@ -18,6 +18,7 @@ class OccBRepReader : public Reader { public: bool readFile(const FilePath& filepath, TaskProgress* progress) override; TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) override; + void applyProperties(const PropertyGroup*) override {} private: TopoDS_Shape m_shape; @@ -29,6 +30,7 @@ class OccBRepWriter : public Writer { public: bool transfer(Span appItems, TaskProgress* progress) override; bool writeFile(const FilePath& filepath, TaskProgress* progress) override; + void applyProperties(const PropertyGroup*) override {} private: TopoDS_Shape m_shape; diff --git a/src/io_occ/io_occ_iges.h b/src/io_occ/io_occ_iges.h index 30755736..ff9aa6f1 100644 --- a/src/io_occ/io_occ_iges.h +++ b/src/io_occ/io_occ_iges.h @@ -21,6 +21,8 @@ class OccStaticVariablesRollback; class OccIgesReader : public Reader { public: OccIgesReader(); + OccIgesReader(const OccIgesReader&) = delete; // Not copyable + OccIgesReader& operator=(const OccIgesReader&) = delete; // Not copyable ~OccIgesReader(); bool readFile(const FilePath& filepath, TaskProgress* progress) override; @@ -67,6 +69,8 @@ class OccIgesReader : public Reader { class OccIgesWriter : public Writer { public: OccIgesWriter(); + OccIgesWriter(const OccIgesWriter&) = delete; // Not copyable + OccIgesWriter& operator=(const OccIgesWriter&) = delete; // Not copyable ~OccIgesWriter(); bool transfer(Span appItems, TaskProgress* progress) override; diff --git a/src/io_occ/io_occ_step.cpp b/src/io_occ/io_occ_step.cpp index 93125bbc..311239a8 100644 --- a/src/io_occ/io_occ_step.cpp +++ b/src/io_occ/io_occ_step.cpp @@ -308,6 +308,14 @@ bool OccStepWriter::transfer(Span appItems, TaskProgress* MayoIO_CafGlobalScopedLock(cafLock); OccStaticVariablesRollback rollback; this->changeStaticVariables(&rollback); + if (m_params.schema != m_schemaLastTransfer) { + // NOTE from $OCC_7.4.0_DIR/doc/pdf/user_guides/occt_step.pdf (page 26) + // For the parameter "write.step.schema" to take effect, method STEPControl_Writer::Model(true) + // should be called after changing this parameter (corresponding command in DRAW is "newmodel") + m_writer->ChangeWriter().Model(true); + m_schemaLastTransfer = m_params.schema; + } + return Private::cafTransfer(*m_writer, appItems, progress); } @@ -355,15 +363,7 @@ void OccStepWriter::applyProperties(const PropertyGroup* group) void OccStepWriter::changeStaticVariables(OccStaticVariablesRollback* rollback) { - const int previousSchema = Interface_Static::IVal("write.step.schema"); rollback->change("write.step.schema", int(m_params.schema)); - if (int(m_params.schema) != previousSchema) { - // NOTE from $OCC_7.4.0_DIR/doc/pdf/user_guides/occt_step.pdf (page 26) - // For the parameter "write.step.schema" to take effect, method STEPControl_Writer::Model(true) - // should be called after changing this parameter (corresponding command in DRAW is "newmodel") - m_writer->ChangeWriter().Model(true); - } - rollback->change("write.step.unit", OccCommon::toCafString(m_params.lengthUnit)); rollback->change("write.step.assembly", int(m_params.assemblyMode)); rollback->change("write.step.vertex.mode", int(m_params.freeVertexMode)); diff --git a/src/io_occ/io_occ_step.h b/src/io_occ/io_occ_step.h index 499e06f0..70ea7e4a 100644 --- a/src/io_occ/io_occ_step.h +++ b/src/io_occ/io_occ_step.h @@ -25,6 +25,8 @@ class OccStaticVariablesRollback; class OccStepReader : public Reader { public: OccStepReader(); + OccStepReader(const OccStepReader&) = delete; // Not copyable + OccStepReader& operator=(const OccStepReader&) = delete; // Not copyable ~OccStepReader(); bool readFile(const FilePath& filepath, TaskProgress* progress) override; @@ -108,6 +110,8 @@ class OccStepReader : public Reader { class OccStepWriter : public Writer { public: OccStepWriter(); + OccStepWriter(const OccStepWriter&) = delete; // Not copyable + OccStepWriter& operator=(const OccStepWriter&) = delete; // Not copyable ~OccStepWriter(); bool transfer(Span appItems, TaskProgress* progress) override; @@ -157,6 +161,7 @@ class OccStepWriter : public Writer { STEPCAFControl_Writer* m_writer = nullptr; std::aligned_storage_t m_writerStorage; Parameters m_params; + Schema m_schemaLastTransfer = Schema::AP214_IS; }; } // namespace IO diff --git a/src/io_occ/io_occ_stl.cpp b/src/io_occ/io_occ_stl.cpp index 396238f9..62e0699e 100644 --- a/src/io_occ/io_occ_stl.cpp +++ b/src/io_occ/io_occ_stl.cpp @@ -8,8 +8,9 @@ #include "../base/application_item.h" #include "../base/brep_utils.h" -#include "../base/document.h" #include "../base/caf_utils.h" +#include "../base/data_triangulation.h" +#include "../base/document.h" #include "../base/filepath_conv.h" #include "../base/occ_progress_indicator.h" #include "../base/property_enumeration.h" @@ -21,7 +22,6 @@ #include #include #include -#include #include namespace Mayo { @@ -81,7 +81,7 @@ TDF_LabelSequence OccStlReader::transfer(DocumentPtr doc, TaskProgress* /*progre return {}; const TDF_Label entityLabel = doc->newEntityLabel(); - TDataXtd_Triangulation::Set(entityLabel, m_mesh); + DataTriangulation::Set(entityLabel, m_mesh); TDataStd_Name::Set(entityLabel, filepathTo(m_baseFilename)); return CafUtils::makeLabelSequence({ entityLabel }); } @@ -104,7 +104,7 @@ bool OccStlWriter::transfer(Span appItems, TaskProgress* m_shape = XCaf::shape(label); } else { - auto attrPolyTri = CafUtils::findAttribute(label); + auto attrPolyTri = CafUtils::findAttribute(label); if (!attrPolyTri.IsNull()) m_mesh = attrPolyTri->Get(); } diff --git a/src/io_occ/io_occ_stl.h b/src/io_occ/io_occ_stl.h index 33b472b3..a55674b8 100644 --- a/src/io_occ/io_occ_stl.h +++ b/src/io_occ/io_occ_stl.h @@ -19,6 +19,7 @@ class OccStlReader : public Reader { public: bool readFile(const FilePath& filepath, TaskProgress* progress) override; TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) override; + void applyProperties(const PropertyGroup*) override {} private: Handle_Poly_Triangulation m_mesh; diff --git a/src/io_ply/commit_miniply.txt b/src/io_ply/commit_miniply.txt new file mode 100644 index 00000000..47f6a3d7 --- /dev/null +++ b/src/io_ply/commit_miniply.txt @@ -0,0 +1 @@ +d7005b901e64c66bfb39e88c4882d006dcf70628 diff --git a/src/io_ply/io_ply_reader.cpp b/src/io_ply/io_ply_reader.cpp new file mode 100644 index 00000000..d1375d00 --- /dev/null +++ b/src/io_ply/io_ply_reader.cpp @@ -0,0 +1,214 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "io_ply_reader.h" +#include "../base/caf_utils.h" +#include "../base/cpp_utils.h" +#include "../base/data_triangulation.h" +#include "../base/document.h" +#include "../base/filepath_conv.h" +#include "../base/mesh_utils.h" +#include "../base/messenger.h" +#include "../base/property_builtins.h" +#include "../base/tkernel_utils.h" +#include "miniply.h" +// TODO Move miniply library files into 3rdparty folder + +#include +#include + +namespace Mayo { +namespace IO { + +bool PlyReader::readFile(const FilePath& filepath, TaskProgress* /*progress*/) +{ + miniply::PLYReader reader(filepath.u8string().c_str()); + if (!reader.valid()) + return false; + + // Reset internal data + m_isValidMesh = false; + m_baseFilename = filepath.stem(); + m_nodeCount = 0; + m_vecNodeCoord.clear(); + m_vecColorComponent.clear(); + m_vecIndex.clear(); + m_vecNormalCoord.clear(); + bool assumeTriangles = true; + + // Guess if PLY faces are triangles + uint32_t faceIdxs[3] = {}; + if (assumeTriangles) { + miniply::PLYElement* faceElem = reader.get_element(reader.find_element(miniply::kPLYFaceElement)); + if (!faceElem) + return {}; + + assumeTriangles = faceElem->convert_list_to_fixed_size(faceElem->find_property("vertex_indices"), 3, faceIdxs); + } + + bool gotVerts = false; + bool gotFaces = false; + while (reader.has_element() && (!gotVerts || !gotFaces)) { + if (reader.element_is(miniply::kPLYVertexElement)) { + uint32_t propIdxs[3] = {}; + if (!reader.load_element() || !reader.find_pos(propIdxs)) + break; + + m_nodeCount = reader.num_rows(); + m_vecNodeCoord.resize(m_nodeCount * 3); + reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::Float, m_vecNodeCoord.data()); + if (reader.find_normal(propIdxs)) { + m_vecNormalCoord.resize(m_nodeCount * 3); + reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::Float, m_vecNormalCoord.data()); + } + + if (reader.find_color(propIdxs)) { + m_vecColorComponent.resize(m_nodeCount * 3); + reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::UChar, m_vecColorComponent.data()); + } + + //if (reader.find_texcoord(propIdxs)) { + // vecUvCoord.resize(nodeCount * 2); + // reader.extract_properties(propIdxs, 2, miniply::PLYPropertyType::Float, vecUvCoord.data()); + //} + + gotVerts = true; + } + else if (!gotFaces && reader.element_is(miniply::kPLYFaceElement)) { + if (!reader.load_element()) + break; + + if (assumeTriangles) { + m_vecIndex.resize(reader.num_rows() * 3); + reader.extract_properties(faceIdxs, 3, miniply::PLYPropertyType::Int, m_vecIndex.data()); + } + else { + uint32_t propIdx = 0; + if (!reader.find_indices(&propIdx)) + break; + + const bool polys = reader.requires_triangulation(propIdx); + if (polys && !gotVerts) { + this->messenger()->emitError("Face data needing triangulation found before vertex data"); + break; + } + + if (polys) { + m_vecIndex.resize(reader.num_triangles(propIdx) * 3); + reader.extract_triangles(propIdx, m_vecNodeCoord.data(), m_nodeCount, miniply::PLYPropertyType::Int, m_vecIndex.data()); + } + else { + m_vecIndex.resize(reader.num_rows() * 3); + reader.extract_list_property(propIdx, miniply::PLYPropertyType::Int, m_vecIndex.data()); + } + } + + gotFaces = true; + } + else if (!gotFaces && reader.element_is("tristrips")) { + if (!reader.load_element()) { + this->messenger()->emitError("Failed to load triangle strips"); + break; + } + + uint32_t propIdx = reader.element()->find_property("vertex_indices"); + if (propIdx == miniply::kInvalidIndex) { + this->messenger()->emitError("Couldn't find 'vertex_indices' property for the 'tristrips' element"); + break; + } + + m_vecIndex.resize(reader.sum_of_list_counts(propIdx)); + reader.extract_list_property(propIdx, miniply::PLYPropertyType::Int, m_vecIndex.data()); + gotFaces = true; + } + + reader.next_element(); + } // endwhile + + auto fnCheckIndices = [](Span spanIndex, uint32_t nodeCount) { + for (int index : spanIndex) { + if (index < 0 || CppUtils::cmpGreaterEqual(index, nodeCount)) + return false; + } + + return true; + }; + + m_isValidMesh = gotVerts && gotFaces && fnCheckIndices(m_vecIndex, m_nodeCount); + return m_isValidMesh; +} + +TDF_LabelSequence PlyReader::transfer(DocumentPtr doc, TaskProgress* /*progress*/) +{ + if (!m_isValidMesh) + return {}; + + // Create target mesh + const int triangleCount = CppUtils::safeStaticCast(m_vecIndex.size() / 3); + Handle_Poly_Triangulation mesh = new Poly_Triangulation(m_nodeCount, triangleCount, false/*hasUvNodes*/); + if (!m_vecNormalCoord.empty()) + MeshUtils::allocateNormals(mesh); + + // Copy nodes(vertices) into mesh + for (int i = 0; CppUtils::cmpLess(i, m_vecNodeCoord.size()); i += 3) { + const auto& vec = m_vecNodeCoord; + const gp_Pnt node = { vec.at(i), vec.at(i + 1), vec.at(i + 2) }; + MeshUtils::setNode(mesh, (i / 3) + 1, node); + } + + // Copy triangles indices into mesh + for (int i = 0; CppUtils::cmpLess(i, m_vecIndex.size()); i += 3) { + const auto& vec = m_vecIndex; + const Poly_Triangle tri = { 1 + vec.at(i), 1 + vec.at(i + 1), 1 + vec.at(i + 2) }; + MeshUtils::setTriangle(mesh, (i / 3) + 1, tri); + } + + // Copy normals(optional) into mesh + for (int i = 0; CppUtils::cmpLess(i, m_vecNormalCoord.size()); i += 3) { + const auto& vec = m_vecNormalCoord; + const MeshUtils::Poly_Triangulation_NormalType n(vec.at(i), vec.at(i + 1), vec.at(i + 2)); + MeshUtils::setNormal(mesh, (i / 3) + 1, n); + } + + // Copy colors(optional) into mesh + std::vector vecColor; + for (int i = 0; CppUtils::cmpLess(i, m_vecColorComponent.size()); i += 3) { + const auto& vec = m_vecColorComponent; + const Quantity_Color color = { + vec.at(i) / 255., vec.at(i + 1) / 255., vec.at(i + 2) / 255., + TKernelUtils::preferredRgbColorType() + }; + vecColor.push_back(color); + } + + // Insert mesh as a document entity + const TDF_Label entityLabel = doc->newEntityLabel(); + DataTriangulation::Set(entityLabel, mesh, vecColor); + TDataStd_Name::Set(entityLabel, filepathTo(m_baseFilename)); + return CafUtils::makeLabelSequence({ entityLabel }); +} + +Span PlyFactoryReader::formats() const +{ + static const Format arrayFormat[] = { Format_PLY }; + return arrayFormat; +} + +std::unique_ptr PlyFactoryReader::create(Format format) const +{ + if (format == Format_PLY) + return std::make_unique(); + + return {}; +} + +std::unique_ptr PlyFactoryReader::createProperties(Format, PropertyGroup*) const +{ + return {}; +} + +} // namespace IO +} // namespace Mayo diff --git a/src/io_ply/io_ply_reader.h b/src/io_ply/io_ply_reader.h new file mode 100644 index 00000000..3642af8c --- /dev/null +++ b/src/io_ply/io_ply_reader.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/io_reader.h" +#include + +namespace Mayo { +namespace IO { + +// Reader for PLY file format based on miniply library +class PlyReader : public Reader { +public: + bool readFile(const FilePath& filepath, TaskProgress* progress) override; + TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) override; + void applyProperties(const PropertyGroup*) override {} + +private: + FilePath m_baseFilename; + uint32_t m_nodeCount = 0; + bool m_isValidMesh = false; + std::vector m_vecNodeCoord; + std::vector m_vecIndex; + std::vector m_vecNormalCoord; + std::vector m_vecColorComponent; +}; + +// Provides factory to create PlyReader objects +class PlyFactoryReader : 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; +}; + +} // namespace IO +} // namespace Mayo diff --git a/src/io_ply/io_ply_writer.cpp b/src/io_ply/io_ply_writer.cpp new file mode 100644 index 00000000..b729376a --- /dev/null +++ b/src/io_ply/io_ply_writer.cpp @@ -0,0 +1,298 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "io_ply_writer.h" + +#include "../base/cpp_utils.h" +#include "../base/document.h" +#include "../base/io_system.h" +#include "../base/math_utils.h" +#include "../base/mesh_access.h" +#include "../base/messenger.h" +#include "../base/property_builtins.h" +#include "../base/property_enumeration.h" +#include "../base/task_progress.h" + +#include + +#include +#include + +#include +#include +#include +#include + +namespace Mayo { +namespace IO { + +namespace { + +enum class Endianness { Unknown, Little, Big }; + +Endianness hostEndianness() +{ + union IntBytes32Convert { + uint32_t integer; + uint8_t bytes[4]; + }; + IntBytes32Convert conv; + conv.integer = 0x01020408; + if (conv.bytes[0] == 0x08 && conv.bytes[3] == 0x01) + return Endianness::Little; + + if (conv.bytes[0] == 0x01 && conv.bytes[3] == 0x08) + return Endianness::Big; + + return Endianness::Unknown; +} + +} // namespace + +struct PlyWriterI18N { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::IO::PlyWriterI18N) +}; + +class PlyWriter::Properties : public PropertyGroup { +public: + Properties(PropertyGroup* parentGroup) + : PropertyGroup(parentGroup) + { + this->targetFormat.mutableEnumeration().changeTrContext(PlyWriterI18N::textIdContext()); + this->comment.setDescription(PlyWriterI18N::textIdTr("Line that will appear in header")); + } + + void restoreDefaults() override { + const PlyWriter::Parameters defaultParams; + this->targetFormat.setValue(defaultParams.format); + this->writeColors.setValue(defaultParams.writeColors); + this->defaultColor.setValue(defaultParams.defaultColor.GetRGB()); + this->comment.setValue(defaultParams.comment); + } + + PropertyEnum targetFormat{ this, PlyWriterI18N::textId("targetFormat") }; + PropertyBool writeColors{ this, PlyWriterI18N::textId("writeColors") }; + PropertyOccColor defaultColor{ this, PlyWriterI18N::textId("defaultColor") }; + PropertyString comment{ this, PlyWriterI18N::textId("comment") }; +}; + +bool PlyWriter::transfer(Span appItems, TaskProgress* progress) +{ + progress = progress ? progress : &TaskProgress::null(); + m_vecNode.clear(); + m_vecNodeColor.clear(); + m_vecFace.clear(); + + // TODO Investigate bad looking 3D mesh when defining vertex colors + // TODO Investigate task abort issue + + // Count number of faces for progress report + int faceCount = 0; + System::traverseUniqueItems(appItems, [&](const DocumentTreeNode& docTreeNode) { + if (docTreeNode.isLeaf()) + IMeshAccess_visitMeshes(docTreeNode, [&](const IMeshAccess&) { ++faceCount; }); + }); + + // Record face meshes + int iFace = 0; + System::traverseUniqueItems(appItems, [&](const DocumentTreeNode& docTreeNode) { + if (docTreeNode.isLeaf() && !progress->isAbortRequested()) { + IMeshAccess_visitMeshes(docTreeNode, [&](const IMeshAccess& mesh) { + this->addMesh(mesh); + progress->setValue(MathUtils::mappedValue(++iFace, 0, faceCount, 0, 100)); + }); + } + }); + + return true; +} + +bool PlyWriter::writeFile(const FilePath& filepath, TaskProgress* progress) +{ + progress = progress ? progress : &TaskProgress::null(); + const bool isBinary = m_params.format == Format::Binary; + std::ios_base::openmode mode = std::ios_base::out; + if (isBinary) + mode |= std::ios_base::binary; + + std::ofstream fstr(filepath, mode); + if (!fstr.is_open()) { + this->messenger()->emitError(PlyWriterI18N::textIdTr("Failed to open file")); + return false; + } + + // Define PLY format + const char* strPlyFormat = nullptr; + if (isBinary) { + const Endianness endian = hostEndianness(); + if (endian == Endianness::Little) + strPlyFormat = "binary_little_endian"; + else if (endian == Endianness::Big) + strPlyFormat = "binary_big_endian"; + else + this->messenger()->emitError(PlyWriterI18N::textIdTr("Unknown host endianness")); + } + else { + strPlyFormat = "ascii"; + } + + if (!strPlyFormat) + return false; + + // Write PLY header + fstr.imbue(std::locale::classic()); + fstr << "ply\n" + << "format " << strPlyFormat << " 1.0\n"; + + if (!m_params.comment.empty()) { + std::string strComment = m_params.comment; + std::replace(strComment.begin(), strComment.end(), '\n', ' '); + std::replace(strComment.begin(), strComment.end(), '\r', ' '); + fstr << "comment " << m_params.comment << "\n"; + } + + fstr << "element vertex " << m_vecNode.size() << "\n" + << "property float x\n" + << "property float y\n" + << "property float z\n"; + + if (m_params.writeColors) { + fstr << "property uchar red\n" + << "property uchar green\n" + << "property uchar blue\n"; + } + + fstr << "element face " << m_vecFace.size() << "\n" + << "property list uchar int vertex_indices\n" + << "end_header\n"; + + // Helpers for progress report + const int elementCount = int(m_vecNode.size() + m_vecFace.size()); + int iElement = 0; + auto fnUpdateProgress = [&]{ + ++iElement; + if (iElement % 50 == 0) { + progress->setValue(MathUtils::mappedValue(iElement, 0, elementCount, 0, 100)); + if (progress->isAbortRequested()) + return false; + } + + return true; + }; + + // Write vertices + for (const Vertex& node : m_vecNode) { + const auto inode = &node - &m_vecNode.front(); + if (isBinary) { + fstr.write(reinterpret_cast(&node.x), 12); + if (m_params.writeColors) + fstr.write(reinterpret_cast(&m_vecNodeColor.at(inode).red), 3); + } + else { + fstr << node.x << " " << node.y << " " << node.z; + if (m_params.writeColors) { + const Color& c = m_vecNodeColor.at(inode); + fstr << " " << int(c.red) << " " << int(c.green) << " " << int(c.blue); + } + + fstr << "\n"; + } + + if (!fnUpdateProgress()) + return true; + } + + fstr.flush(); + // Write face indices + for (const Face& face : m_vecFace) { + if (isBinary) { + const uint8_t indexCount = 3; + fstr.write(reinterpret_cast(&indexCount), 1); + fstr.write(reinterpret_cast(&face.v1), 12); + } + else { + fstr << "3 " << face.v1 << " " << face.v2 << " " << face.v3 << "\n"; + } + + if (!fnUpdateProgress()) + return true; + } + + fstr.flush(); + return true; +} + +std::unique_ptr PlyWriter::createProperties(PropertyGroup* parentGroup) +{ + return std::make_unique(parentGroup); +} + +void PlyWriter::applyProperties(const PropertyGroup* params) +{ + auto ptr = dynamic_cast(params); + if (ptr) { + m_params.format = ptr->targetFormat; + m_params.writeColors = ptr->writeColors; + m_params.defaultColor = Quantity_ColorRGBA(ptr->defaultColor); + m_params.comment = ptr->comment; + } +} + +void PlyWriter::addMesh(const IMeshAccess& mesh) +{ + const Handle(Poly_Triangulation)& triangulation = mesh.triangulation(); + for (int i = 1; i <= triangulation->NbTriangles(); ++i) { + const Poly_Triangle& triangle = triangulation->Triangle(i); + const int32_t offset = CppUtils::safeStaticCast(m_vecNode.size()); + const Face face{ + offset + triangle(1) - 1, offset + triangle(2) - 1, offset + triangle(3) - 1 + }; + m_vecFace.push_back(std::move(face)); + } + + for (int i = 1; i <= triangulation->NbNodes(); ++i) { + const gp_Pnt node = triangulation->Node(i).Transformed(mesh.location()); + const Vertex vertex{ float(node.X()), float(node.Y()), float(node.Z()) }; + m_vecNode.push_back(std::move(vertex)); + } + + if (m_params.writeColors) { + // Helper color conversion function + auto fnColor = [](const Quantity_Color& c) -> Color { + return { uint8_t(c.Red() * 255), uint8_t(c.Green() * 255), uint8_t(c.Blue() * 255) }; + }; + for (int i = 0; i < triangulation->NbNodes(); ++i) { + const std::optional nodeColor = mesh.nodeColor(i); + const Quantity_Color& defaultNodeColor = m_params.defaultColor.GetRGB(); + m_vecNodeColor.push_back(fnColor(nodeColor ? nodeColor.value() : defaultNodeColor)); + } + } +} + +Span PlyFactoryWriter::formats() const +{ + static const Format arrayFormat[] = { Format_PLY }; + return arrayFormat; +} + +std::unique_ptr PlyFactoryWriter::create(Format format) const +{ + if (format == Format_PLY) + return std::make_unique(); + + return {}; +} + +std::unique_ptr PlyFactoryWriter::createProperties(Format format, PropertyGroup* parentGroup) const +{ + if (format == Format_PLY) + return PlyWriter::createProperties(parentGroup); + + return {}; +} + +} // namespace IO +} // namespace Mayo diff --git a/src/io_ply/io_ply_writer.h b/src/io_ply/io_ply_writer.h new file mode 100644 index 00000000..f6167f1b --- /dev/null +++ b/src/io_ply/io_ply_writer.h @@ -0,0 +1,64 @@ +/**************************************************************************** +** Copyright (c) 2022, 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_writer.h" +#include +#include + +namespace Mayo { class IMeshAccess; } + +namespace Mayo { +namespace IO { + +// Writer for PLY file format based on the msh_ply library +class PlyWriter : public Writer { +public: + bool transfer(Span appItems, TaskProgress* progress) override; + bool writeFile(const FilePath& filepath, TaskProgress* progress) override; + + static std::unique_ptr createProperties(PropertyGroup* parentGroup); + void applyProperties(const PropertyGroup* params) override; + + // Parameters + enum class Format { Ascii, Binary }; + + struct Parameters { + Format format = Format::Binary; + bool writeColors = true; + Quantity_ColorRGBA defaultColor{ Quantity_Color(Quantity_NOC_GRAY) }; + std::string comment; + // TODO bool writeNormals = false; + // TODO bool writeEdges = true; + }; + Parameters& parameters() { return m_params; } + const Parameters& constParameters() const { return m_params; } + +private: + struct Vertex { float x; float y; float z; }; + struct Color { uint8_t red; uint8_t green; uint8_t blue; }; + struct Face { int32_t v1; int32_t v2; int32_t v3; }; + void addMesh(const IMeshAccess& mesh); + + class Properties; + Parameters m_params; + std::vector m_vecNode; + std::vector m_vecNodeColor; + std::vector m_vecFace; +}; + +// Provides factory to create PlyWriter objects +class PlyFactoryWriter : public FactoryWriter { +public: + Span formats() const override; + std::unique_ptr create(Format format) const override; + std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; +}; + +} // namespace IO +} // namespace Mayo diff --git a/src/io_ply/miniply.cpp b/src/io_ply/miniply.cpp new file mode 100644 index 00000000..04a2dfeb --- /dev/null +++ b/src/io_ply/miniply.cpp @@ -0,0 +1,2057 @@ +/* +MIT License + +Copyright (c) 2019 Vilya Harvey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include "miniply.h" + +#include +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#endif + + +namespace miniply { + + // + // Public constants + // + + // Standard PLY element names + const char* kPLYVertexElement = "vertex"; + const char* kPLYFaceElement = "face"; + + + // + // PLY constants + // + + static constexpr uint32_t kPLYReadBufferSize = 128 * 1024; + static constexpr uint32_t kPLYTempBufferSize = kPLYReadBufferSize; + + static const char* kPLYFileTypes[] = { "ascii", "binary_little_endian", "binary_big_endian", nullptr }; + static const uint32_t kPLYPropertySize[]= { 1, 1, 2, 2, 4, 4, 4, 8 }; + + struct PLYTypeAlias { + const char* name; + PLYPropertyType type; + }; + + static const PLYTypeAlias kTypeAliases[] = { + { "char", PLYPropertyType::Char }, + { "uchar", PLYPropertyType::UChar }, + { "short", PLYPropertyType::Short }, + { "ushort", PLYPropertyType::UShort }, + { "int", PLYPropertyType::Int }, + { "uint", PLYPropertyType::UInt }, + { "float", PLYPropertyType::Float }, + { "float32",PLYPropertyType::Float }, + { "float64",PLYPropertyType::Double }, + { "double", PLYPropertyType::Double }, + + { "uint8", PLYPropertyType::UChar }, + { "uint16", PLYPropertyType::UShort }, + { "uint32", PLYPropertyType::UInt }, + + { "int8", PLYPropertyType::Char }, + { "int16", PLYPropertyType::Short }, + { "int32", PLYPropertyType::Int }, + + { nullptr, PLYPropertyType::None } + }; + + + // + // Constants + // + + static constexpr double kDoubleDigits[10] = { 0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 }; + + static constexpr float kPi = 3.14159265358979323846f; + + + // + // Vec2 type + // + + struct Vec2 { + float x, y; + }; + + static inline Vec2 operator - (Vec2 lhs, Vec2 rhs) { return Vec2{ lhs.x - rhs.x, lhs.y - rhs.y }; } + + static inline float dot(Vec2 lhs, Vec2 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y; } + static inline float length(Vec2 v) { return std::sqrt(dot(v, v)); } + static inline Vec2 normalize(Vec2 v) { float len = length(v); return Vec2{ v.x / len, v.y / len }; } + + + // + // Vec3 type + // + + struct Vec3 { + float x, y, z; + }; + + static inline Vec3 operator - (Vec3 lhs, Vec3 rhs) { return Vec3{ lhs.x - rhs.x, lhs.y - rhs.y, lhs.z - rhs.z }; } + + static inline float dot(Vec3 lhs, Vec3 rhs) { return lhs.x * rhs.x + lhs.y * rhs.y + lhs.z * rhs.z; } + static inline float length(Vec3 v) { return std::sqrt(dot(v, v)); } + static inline Vec3 normalize(Vec3 v) { float len = length(v); return Vec3{ v.x / len, v.y / len, v.z / len }; } + static inline Vec3 cross(Vec3 lhs, Vec3 rhs) { return Vec3{ lhs.y * rhs.z - lhs.z * rhs.y, lhs.z * rhs.x - lhs.x * rhs.z, lhs.x * rhs.y - lhs.y * rhs.x }; } + + + // + // Internal-only functions + // + + static inline bool is_whitespace(char ch) + { + return ch == ' ' || ch == '\t' || ch == '\r'; + } + + + static inline bool is_digit(char ch) + { + return ch >= '0' && ch <= '9'; + } + + + static inline bool is_letter(char ch) + { + ch |= 32; // upper and lower case letters differ only at this bit. + return ch >= 'a' && ch <= 'z'; + } + + + static inline bool is_alnum(char ch) + { + return is_digit(ch) || is_letter(ch); + } + + + static inline bool is_keyword_start(char ch) + { + return is_letter(ch) || ch == '_'; + } + + + static inline bool is_keyword_part(char ch) + { + return is_alnum(ch) || ch == '_'; + } + + + static inline bool is_safe_buffer_end(char ch) + { + return (ch > 0 && ch <= 32) || (ch >= 127); + } + + + static int file_open(FILE** f, const char* filename, const char* mode) + { + #ifdef _WIN32 + return fopen_s(f, filename, mode); + #else + *f = fopen(filename, mode); + return (*f != nullptr) ? 0 : errno; + #endif + } + + + static inline int file_seek(FILE* file, int64_t offset, int origin) + { + #ifdef _WIN32 + return _fseeki64(file, offset, origin); + #else + static_assert(sizeof(off_t) == sizeof(int64_t), "off_t is not 64 bits."); + return fseeko(file, offset, origin); + #endif + } + + + static bool int_literal(const char* start, char const** end, int* val) + { + const char* pos = start; + + bool negative = false; + if (*pos == '-') { + negative = true; + ++pos; + } + else if (*pos == '+') { + ++pos; + } + + bool hasLeadingZeroes = *pos == '0'; + if (hasLeadingZeroes) { + do { + ++pos; + } while (*pos == '0'); + } + + int numDigits = 0; + int localVal = 0; + while (is_digit(*pos)) { + // FIXME: this will overflow if we get too many digits. + localVal = localVal * 10 + static_cast(*pos - '0'); + ++numDigits; + ++pos; + } + + if (numDigits == 0 && hasLeadingZeroes) { + numDigits = 1; + } + + if (numDigits == 0 || is_letter(*pos) || *pos == '_') { + return false; + } + else if (numDigits > 10) { + // Overflow, literal value is larger than an int can hold. + // FIXME: this won't catch *all* cases of overflow, make it exact. + return false; + } + + if (val != nullptr) { + *val = negative ? -localVal : localVal; + } + if (end != nullptr) { + *end = pos; + } + return true; + } + + + static bool double_literal(const char* start, char const** end, double* val) + { + const char* pos = start; + + bool negative = false; + if (*pos == '-') { + negative = true; + ++pos; + } + else if (*pos == '+') { + ++pos; + } + + double localVal = 0.0; + + bool hasIntDigits = is_digit(*pos); + if (hasIntDigits) { + do { + localVal = localVal * 10.0 + kDoubleDigits[*pos - '0']; + ++pos; + } while (is_digit(*pos)); + } + else if (*pos != '.') { +// set_error("Not a floating point number"); + return false; + } + + bool hasFracDigits = false; + if (*pos == '.') { + ++pos; + hasFracDigits = is_digit(*pos); + if (hasFracDigits) { + double scale = 0.1; + do { + localVal += scale * kDoubleDigits[*pos - '0']; + scale *= 0.1; + ++pos; + } while (is_digit(*pos)); + } + else if (!hasIntDigits) { +// set_error("Floating point number has no digits before or after the decimal point"); + return false; + } + } + + bool hasExponent = *pos == 'e' || *pos == 'E'; + if (hasExponent) { + ++pos; + bool negativeExponent = false; + if (*pos == '-') { + negativeExponent = true; + ++pos; + } + else if (*pos == '+') { + ++pos; + } + + if (!is_digit(*pos)) { +// set_error("Floating point exponent has no digits"); + return false; // error: exponent part has no digits. + } + + double exponent = 0.0; + do { + exponent = exponent * 10.0 + kDoubleDigits[*pos - '0']; + ++pos; + } while (is_digit(*pos)); + + if (val != nullptr) { + if (negativeExponent) { + exponent = -exponent; + } + localVal *= std::pow(10.0, exponent); + } + } + + if (*pos == '.' || *pos == '_' || is_alnum(*pos)) { +// set_error("Floating point number has trailing chars"); + return false; + } + + if (negative) { + localVal = -localVal; + } + + if (val != nullptr) { + *val = localVal; + } + if (end != nullptr) { + *end = pos; + } + return true; + } + + + static bool float_literal(const char* start, char const** end, float* val) + { + double tmp = 0.0; + bool ok = double_literal(start, end, &tmp); + if (ok && val != nullptr) { + *val = static_cast(tmp); + } + return ok; + } + + + static inline void endian_swap_2(uint8_t* data) + { + uint16_t tmp = *reinterpret_cast(data); + tmp = static_cast((tmp >> 8) | (tmp << 8)); + *reinterpret_cast(data) = tmp; + } + + + static inline void endian_swap_4(uint8_t* data) + { + uint32_t tmp = *reinterpret_cast(data); + tmp = (tmp >> 16) | (tmp << 16); + tmp = ((tmp & 0xFF00FF00) >> 8) | ((tmp & 0x00FF00FF) << 8); + *reinterpret_cast(data) = tmp; + } + + + static inline void endian_swap_8(uint8_t* data) + { + uint64_t tmp = *reinterpret_cast(data); + tmp = (tmp >> 32) | (tmp << 32); + tmp = ((tmp & 0xFFFF0000FFFF0000) >> 16) | ((tmp & 0x0000FFFF0000FFFF) << 16); + tmp = ((tmp & 0xFF00FF00FF00FF00) >> 8) | ((tmp & 0x00FF00FF00FF00FF) << 8); + *reinterpret_cast(data) = tmp; + } + + + static inline void endian_swap(uint8_t* data, PLYPropertyType type) + { + switch (kPLYPropertySize[uint32_t(type)]) { + case 2: endian_swap_2(data); break; + case 4: endian_swap_4(data); break; + case 8: endian_swap_8(data); break; + default: break; + } + } + + + static inline void endian_swap_array(uint8_t* data, PLYPropertyType type, int n) + { + switch (kPLYPropertySize[uint32_t(type)]) { + case 2: + for (const uint8_t* end = data + 2 * n; data < end; data += 2) { + endian_swap_2(data); + } + break; + case 4: + for (const uint8_t* end = data + 4 * n; data < end; data += 4) { + endian_swap_4(data); + } + break; + case 8: + for (const uint8_t* end = data + 8 * n; data < end; data += 8) { + endian_swap_8(data); + } + break; + default: + break; + } + } + + + template + static void copy_and_convert_to(T* dest, const uint8_t* src, PLYPropertyType srcType) + { + switch (srcType) { + case PLYPropertyType::Char: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::UChar: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::Short: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::UShort: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::Int: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::UInt: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::Float: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::Double: *dest = static_cast(*reinterpret_cast(src)); break; + case PLYPropertyType::None: break; + } + } + + + static void copy_and_convert(uint8_t* dest, PLYPropertyType destType, const uint8_t* src, PLYPropertyType srcType) + { + switch (destType) { + case PLYPropertyType::Char: copy_and_convert_to(reinterpret_cast (dest), src, srcType); break; + case PLYPropertyType::UChar: copy_and_convert_to(reinterpret_cast (dest), src, srcType); break; + case PLYPropertyType::Short: copy_and_convert_to(reinterpret_cast (dest), src, srcType); break; + case PLYPropertyType::UShort: copy_and_convert_to(reinterpret_cast(dest), src, srcType); break; + case PLYPropertyType::Int: copy_and_convert_to(reinterpret_cast (dest), src, srcType); break; + case PLYPropertyType::UInt: copy_and_convert_to(reinterpret_cast(dest), src, srcType); break; + case PLYPropertyType::Float: copy_and_convert_to(reinterpret_cast (dest), src, srcType); break; + case PLYPropertyType::Double: copy_and_convert_to(reinterpret_cast (dest), src, srcType); break; + case PLYPropertyType::None: break; + } + } + + + static inline bool compatible_types(PLYPropertyType srcType, PLYPropertyType destType) + { + return (srcType == destType) || + (srcType < PLYPropertyType::Float && (uint32_t(srcType) ^ 0x1) == uint32_t(destType)); + } + + + // + // PLYElement methods + // + + void PLYElement::calculate_offsets() + { + fixedSize = true; + for (PLYProperty& prop : properties) { + if (prop.countType != PLYPropertyType::None) { + fixedSize = false; + break; + } + } + + // Note that each list property gets its own separate storage. Only fixed + // size properties go into the common data block. The `rowStride` is the + // size of a row in the common data block. + rowStride = 0; + for (PLYProperty& prop : properties) { + if (prop.countType != PLYPropertyType::None) { + continue; + } + prop.offset = rowStride; + rowStride += kPLYPropertySize[uint32_t(prop.type)]; + } + } + + + uint32_t PLYElement::find_property(const char *propName) const + { + for (uint32_t i = 0, endI = uint32_t(properties.size()); i < endI; i++) { + if (strcmp(propName, properties.at(i).name.c_str()) == 0) { + return i; + } + } + return kInvalidIndex; + } + + + bool PLYElement::find_properties(uint32_t propIdxs[], uint32_t numIdxs, ...) const + { + va_list args; + va_start(args, numIdxs); + bool foundAll = find_properties_va(propIdxs, numIdxs, args); + va_end(args); + return foundAll; + } + + + bool PLYElement::find_properties_va(uint32_t propIdxs[], uint32_t numIdxs, va_list names) const + { + for (uint32_t i = 0; i < numIdxs; i++) { + propIdxs[i] = find_property(va_arg(names, const char*)); + if (propIdxs[i] == kInvalidIndex) { + return false; + } + } + return true; + } + + + bool PLYElement::convert_list_to_fixed_size(uint32_t listPropIdx, uint32_t listSize, uint32_t newPropIdxs[]) + { + if (fixedSize || listPropIdx >= properties.size() || properties[listPropIdx].countType == PLYPropertyType::None) { + return false; + } + + PLYProperty oldListProp = properties[listPropIdx]; + + // If the generated names are less than 256 chars, we will use an array on + // the stack as temporary storage. In the rare case that they're longer, + // we'll allocate an array of sufficient size on the heap and use that + // instead. This means we'll avoid allocating in all but the most extreme + // cases. + char inlineBuf[256]; + size_t nameBufSize = oldListProp.name.size() + 12; // the +12 allows space for an '_', a number up to 10 digits long and the terminating null. + char* nameBuf = inlineBuf; + if (nameBufSize > sizeof(inlineBuf)) { + nameBuf = new char[nameBufSize]; + } + + // Set up a property for the list count column. + PLYProperty& countProp = properties[listPropIdx]; + snprintf(nameBuf, nameBufSize, "%s_count", oldListProp.name.c_str()); + countProp.name = nameBuf; + countProp.type = oldListProp.countType; + countProp.countType = PLYPropertyType::None; + countProp.stride = kPLYPropertySize[uint32_t(oldListProp.countType)]; + + if (listSize > 0) { + // Set up additional properties for the list entries, 1 per entry. + if (listPropIdx + 1 == properties.size()) { + properties.resize(properties.size() + listSize); + } + else { + properties.insert(properties.begin() + listPropIdx + 1, listSize, PLYProperty()); + } + + for (uint32_t i = 0; i < listSize; i++) { + uint32_t propIdx = listPropIdx + 1 + i; + + PLYProperty& itemProp = properties[propIdx]; + snprintf(nameBuf, sizeof(nameBuf), "%s_%u", oldListProp.name.c_str(), i); + itemProp.name = nameBuf; + itemProp.type = oldListProp.type; + itemProp.countType = PLYPropertyType::None; + itemProp.stride = kPLYPropertySize[uint32_t(oldListProp.type)]; + + newPropIdxs[i] = propIdx; + } + } + + if (nameBuf != inlineBuf) { + delete[] nameBuf; + } + + calculate_offsets(); + return true; + } + + + // + // PLYReader methods + // + + PLYReader::PLYReader(const char* filename) + { + m_buf = new char[kPLYReadBufferSize + 1]; + m_buf[kPLYReadBufferSize] = '\0'; + + m_tmpBuf = new char[kPLYTempBufferSize + 1]; + m_tmpBuf[kPLYTempBufferSize] = '\0'; + + m_bufEnd = m_buf + kPLYReadBufferSize; + m_pos = m_bufEnd; + m_end = m_bufEnd; + + if (file_open(&m_f, filename, "rb") != 0) { + m_f = nullptr; + m_valid = false; + return; + } + m_valid = true; + + refill_buffer(); + + m_valid = keyword("ply") && next_line() && + keyword("format") && advance() && + typed_which(kPLYFileTypes, &m_fileType) && advance() && + int_literal(&m_majorVersion) && advance() && + match(".") && advance() && + int_literal(&m_minorVersion) && next_line() && + parse_elements() && + keyword("end_header") && advance() && match("\n") && accept(); + if (!m_valid) { + return; + } + m_inDataSection = true; + if (m_fileType == PLYFileType::ASCII) { + advance(); + } + + for (PLYElement& elem : m_elements) { + elem.calculate_offsets(); + } + } + + + PLYReader::~PLYReader() + { + if (m_f != nullptr) { + fclose(m_f); + } + delete[] m_buf; + delete[] m_tmpBuf; + } + + + bool PLYReader::valid() const + { + return m_valid; + } + + + bool PLYReader::has_element() const + { + return m_valid && m_currentElement < m_elements.size(); + } + + + const PLYElement* PLYReader::element() const + { + assert(has_element()); + return &m_elements[m_currentElement]; + } + + + bool PLYReader::load_element() + { + assert(has_element()); + if (m_elementLoaded) { + return true; + } + + PLYElement& elem = m_elements[m_currentElement]; + return elem.fixedSize ? load_fixed_size_element(elem) : load_variable_size_element(elem); + } + + + void PLYReader::next_element() + { + if (!has_element()) { + return; + } + + // If the element was loaded, the read buffer should already be positioned at + // the start of the next element. + PLYElement& elem = m_elements[m_currentElement]; + m_currentElement++; + + if (m_elementLoaded) { + // Clear any temporary storage used for list properties in the current element. + for (PLYProperty& prop : elem.properties) { + if (prop.countType == PLYPropertyType::None) { + continue; + } + prop.listData.clear(); + prop.listData.shrink_to_fit(); + prop.rowCount.clear(); + prop.rowCount.shrink_to_fit(); + } + + // Clear temporary storage for the non-list properties in the current element. + m_elementData.clear(); + m_elementLoaded = false; + return; + } + + // If the element wasn't loaded, we have to move the file pointer past its + // contents. How we do that depends on whether this is an ASCII or binary + // file and, if it's a binary, whether the element is fixed or variable + // size. + if (m_fileType == PLYFileType::ASCII) { + for (uint32_t row = 0; row < elem.count; row++) { + next_line(); + } + } + else if (elem.fixedSize) { + int64_t elementStart = static_cast(m_pos - m_buf); + int64_t elementSize = elem.rowStride * elem.count; + int64_t elementEnd = elementStart + elementSize; + if (elementEnd >= kPLYReadBufferSize) { + m_bufOffset += elementEnd; + file_seek(m_f, m_bufOffset, SEEK_SET); + m_bufEnd = m_buf + kPLYReadBufferSize; + m_pos = m_bufEnd; + m_end = m_bufEnd; + refill_buffer(); + } + else { + m_pos = m_buf + elementEnd; + m_end = m_pos; + } + } + else if (m_fileType == PLYFileType::Binary) { + for (uint32_t row = 0; row < elem.count; row++) { + for (const PLYProperty& prop : elem.properties) { + if (prop.countType == PLYPropertyType::None) { + uint32_t numBytes = kPLYPropertySize[uint32_t(prop.type)]; + if (m_pos + numBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + numBytes > m_bufEnd) { + m_valid = false; + return; + } + } + m_pos += numBytes; + m_end = m_pos; + continue; + } + + uint32_t numBytes = kPLYPropertySize[uint32_t(prop.countType)]; + if (m_pos + numBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + numBytes > m_bufEnd) { + m_valid = false; + return; + } + } + + int count = 0; + copy_and_convert_to(&count, reinterpret_cast(m_pos), prop.countType); + + if (count < 0) { + m_valid = false; + return; + } + + numBytes += uint32_t(count) * kPLYPropertySize[uint32_t(prop.type)]; + if (m_pos + numBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + numBytes > m_bufEnd) { + m_valid = false; + return; + } + } + m_pos += numBytes; + m_end = m_pos; + } + } + } + else { // PLYFileType::BinaryBigEndian + for (uint32_t row = 0; row < elem.count; row++) { + for (const PLYProperty& prop : elem.properties) { + if (prop.countType == PLYPropertyType::None) { + uint32_t numBytes = kPLYPropertySize[uint32_t(prop.type)]; + if (m_pos + numBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + numBytes > m_bufEnd) { + m_valid = false; + return; + } + } + m_pos += numBytes; + m_end = m_pos; + continue; + } + + uint32_t numBytes = kPLYPropertySize[uint32_t(prop.countType)]; + if (m_pos + numBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + numBytes > m_bufEnd) { + m_valid = false; + return; + } + } + + int count = 0; + uint8_t tmp[8]; + memcpy(tmp, m_pos, numBytes); + endian_swap(tmp, prop.countType); + copy_and_convert_to(&count, tmp, prop.countType); + + if (count < 0) { + m_valid = false; + return; + } + + numBytes += uint32_t(count) * kPLYPropertySize[uint32_t(prop.type)]; + if (m_pos + numBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + numBytes > m_bufEnd) { + m_valid = false; + return; + } + } + + m_pos += numBytes; + m_end = m_pos; + } + } + } + } + + + PLYFileType PLYReader::file_type() const + { + return m_fileType; + } + + + int PLYReader::version_major() const + { + return m_majorVersion; + } + + + int PLYReader::version_minor() const + { + return m_minorVersion; + } + + + uint32_t PLYReader::num_elements() const + { + return m_valid ? static_cast(m_elements.size()) : 0; + } + + + uint32_t PLYReader::find_element(const char* name) const + { + for (uint32_t i = 0, endI = num_elements(); i < endI; i++) { + const PLYElement& elem = m_elements[i]; + if (strcmp(elem.name.c_str(), name) == 0) { + return i; + } + } + return kInvalidIndex; + } + + + PLYElement* PLYReader::get_element(uint32_t idx) + { + return (idx < num_elements()) ? &m_elements[idx] : nullptr; + } + + + bool PLYReader::element_is(const char* name) const + { + return has_element() && strcmp(element()->name.c_str(), name) == 0; + } + + + uint32_t PLYReader::num_rows() const + { + return has_element() ? element()->count : 0; + } + + + uint32_t PLYReader::find_property(const char* name) const + { + return has_element() ? element()->find_property(name) : kInvalidIndex; + } + + + bool PLYReader::find_properties(uint32_t propIdxs[], uint32_t numIdxs, ...) const + { + if (!has_element()) { + return false; + } + va_list args; + va_start(args, numIdxs); + bool foundAll = element()->find_properties_va(propIdxs, numIdxs, args); + va_end(args); + return foundAll; + } + + + bool PLYReader::extract_properties(const uint32_t propIdxs[], uint32_t numProps, PLYPropertyType destType, void *dest) const + { + if (numProps == 0) { + return false; + } + + const PLYElement* elem = element(); + + // Make sure all property indexes are valid and that none of the properties + // are lists (this function only extracts non-list data). + for (uint32_t i = 0; i < numProps; i++) { + if (propIdxs[i] >= elem->properties.size()) { + return false; + } + } + + // Find out whether we have contiguous columns. If so, we may be able to + // use a more efficient data extraction technique. + bool contiguousCols = true; + uint32_t expectedOffset = elem->properties[propIdxs[0]].offset; + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + if (prop.offset != expectedOffset) { + contiguousCols = false; + break; + } + expectedOffset = prop.offset + kPLYPropertySize[uint32_t(prop.type)]; + } + + // If the row we're extracting is contiguous in memory (i.e. there are no + // gaps anywhere in a row - start, end or middle), we can use an even MORE + // efficient data extraction technique. + bool contiguousRows = contiguousCols && + (elem->properties[propIdxs[0]].offset == 0) && + (expectedOffset == elem->rowStride); + + // If no data conversion is required, we can memcpy chunks of data + // directly over to `dest`. How big those chunks will be depends on whether + // the columns and/or rows are contiguous, as determined above. + bool conversionRequired = false; + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + if (!compatible_types(prop.type, destType)) { + conversionRequired = true; + break; + } + } + + uint8_t* to = reinterpret_cast(dest); + if (!conversionRequired) { + // If no data conversion is required, we can just use memcpy to get + // values into dest. + if (contiguousRows) { + // Most efficient case is when the rows are contiguous. It means we're + // simply copying the entire data block for this element, which we can + // do with a single memcpy. + std::memcpy(to, m_elementData.data(), m_elementData.size()); + } + else if (contiguousCols) { + // If the rows aren't contiguous, but the columns we're extracting + // within each row are, then we can do a single memcpy per row. + const uint8_t* from = m_elementData.data() + elem->properties[propIdxs[0]].offset; + const uint8_t* end = m_elementData.data() + m_elementData.size(); + const size_t numBytes = expectedOffset - elem->properties[propIdxs[0]].offset; + while (from < end) { + std::memcpy(to, from, numBytes); + from += elem->rowStride; + to += numBytes; + } + } + else { + // If the columns aren't contiguous, we must memcpy each one separately. + const uint8_t* row = m_elementData.data(); + const uint8_t* end = m_elementData.data() + m_elementData.size(); + uint8_t* to = reinterpret_cast(dest); + size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes. + while (row < end) { + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + std::memcpy(to, row + prop.offset, colBytes); + to += colBytes; + } + row += elem->rowStride; + } + } + } + else { + // We will have to do data type conversions on the column values here. We + // cannot simply use memcpy in this case, every column has to be + // processed separately. + const uint8_t* row = m_elementData.data(); + const uint8_t* end = m_elementData.data() + m_elementData.size(); + uint8_t* to = reinterpret_cast(dest); + size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes. + while (row < end) { + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + copy_and_convert(to, destType, row + prop.offset, prop.type); + to += colBytes; + } + row += elem->rowStride; + } + } + + return true; + } + + + bool PLYReader::extract_properties_with_stride(const uint32_t propIdxs[], uint32_t numProps, PLYPropertyType destType, void *dest, uint32_t destStride) const + { + if (numProps == 0) { + return false; + } + + // The destination stride must be greater than or equal to the combined + // size of all properties we're extracting. Zero is treated as a special + // value meaning packed with no spacing. + const uint32_t minDestStride = numProps * kPLYPropertySize[uint32_t(destType)]; + if (destStride == 0 || destStride == minDestStride) { + return extract_properties(propIdxs, numProps, destType, dest); + } + else if (destStride < minDestStride) { + return false; + } + + const PLYElement* elem = element(); + + // Make sure all property indexes are valid and that none of the properties + // are lists (this function only extracts non-list data). + for (uint32_t i = 0; i < numProps; i++) { + if (propIdxs[i] >= elem->properties.size()) { + return false; + } + } + + // Find out whether we have contiguous columns. If so, we may be able to + // use a more efficient data extraction technique. + bool contiguousCols = true; + uint32_t expectedOffset = elem->properties[propIdxs[0]].offset; + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + if (prop.offset != expectedOffset) { + contiguousCols = false; + break; + } + expectedOffset = prop.offset + kPLYPropertySize[uint32_t(prop.type)]; + } + + // If no data conversion is required, we can memcpy chunks of data + // directly over to `dest`. How big those chunks will be depends on whether + // the columns and/or rows are contiguous, as determined above. + bool conversionRequired = false; + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + if (!compatible_types(prop.type, destType)) { + conversionRequired = true; + break; + } + } + + uint8_t* to = reinterpret_cast(dest); + if (!conversionRequired) { + // If no data conversion is required, we can just use memcpy to get + // values into dest. When the destination requires some padding between + // rows, the best we can do is a memcpy per row. + if (contiguousCols) { + // If the rows aren't contiguous, but the columns we're extracting + // within each row are, then we can do a single memcpy per row. + const uint8_t* from = m_elementData.data() + elem->properties[propIdxs[0]].offset; + const uint8_t* end = m_elementData.data() + m_elementData.size(); + const size_t numBytes = expectedOffset - elem->properties[propIdxs[0]].offset; + while (from < end) { + std::memcpy(to, from, numBytes); + from += elem->rowStride; + to += destStride; + } + } + else { + // If the columns aren't contiguous, we must memcpy each one separately. + const uint8_t* row = m_elementData.data(); + const uint8_t* end = m_elementData.data() + m_elementData.size(); + uint8_t* to = reinterpret_cast(dest); + const size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes. + const size_t colPadding = destStride - minDestStride; + while (row < end) { + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + std::memcpy(to, row + prop.offset, colBytes); + to += colBytes; + } + row += elem->rowStride; + to += colPadding; + } + } + } + else { + // We will have to do data type conversions on the column values here. We + // cannot simply use memcpy in this case, every column has to be + // processed separately. + const uint8_t* row = m_elementData.data(); + const uint8_t* end = m_elementData.data() + m_elementData.size(); + uint8_t* to = reinterpret_cast(dest); + size_t colBytes = kPLYPropertySize[uint32_t(destType)]; // size of an output column in bytes. + size_t colPadding = destStride - minDestStride; + while (row < end) { + for (uint32_t i = 0; i < numProps; i++) { + uint32_t propIdx = propIdxs[i]; + const PLYProperty& prop = elem->properties[propIdx]; + copy_and_convert(to, destType, row + prop.offset, prop.type); + to += colBytes; + } + row += elem->rowStride; + to += colPadding; + } + } + + return true; + } + + + const uint32_t* PLYReader::get_list_counts(uint32_t propIdx) const + { + if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) { + return nullptr; + } + return element()->properties[propIdx].rowCount.data(); + } + + + uint32_t PLYReader::sum_of_list_counts(uint32_t propIdx) const + { + if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) { + return 0; + } + const PLYProperty& prop = element()->properties[propIdx]; + return prop.listData.size() / kPLYPropertySize[uint32_t(prop.type)]; + } + + + const uint8_t* PLYReader::get_list_data(uint32_t propIdx) const + { + if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) { + return nullptr; + } + return element()->properties[propIdx].listData.data(); + } + + + bool PLYReader::extract_list_property(uint32_t propIdx, PLYPropertyType destType, void *dest) const + { + if (!has_element() || propIdx >= element()->properties.size() || element()->properties[propIdx].countType == PLYPropertyType::None) { + return false; + } + + const PLYProperty& prop = element()->properties[propIdx]; + if (compatible_types(prop.type, destType)) { + // If no type conversion is required, we can just copy the list data + // directly over with a single memcpy. + std::memcpy(dest, prop.listData.data(), prop.listData.size()); + } + else { + // If type conversion is required we'll have to process each list value separately. + const uint8_t* from = prop.listData.data(); + const uint8_t* end = prop.listData.data() + prop.listData.size(); + uint8_t* to = reinterpret_cast(dest); + const size_t toBytes = kPLYPropertySize[uint32_t(destType)]; + const size_t fromBytes = kPLYPropertySize[uint32_t(prop.type)]; + while (from < end) { + copy_and_convert(to, destType, from, prop.type); + to += toBytes; + from += fromBytes; + } + } + + return true; + } + + + uint32_t PLYReader::num_triangles(uint32_t propIdx) const + { + const uint32_t* counts = get_list_counts(propIdx); + if (counts == nullptr) { + return 0; + } + + const uint32_t numRows = element()->count; + uint32_t num = 0; + for (uint32_t i = 0; i < numRows; i++) { + if (counts[i] >= 3) { + num += counts[i] - 2; + } + } + return num; + } + + + bool PLYReader::requires_triangulation(uint32_t propIdx) const + { + const uint32_t* counts = get_list_counts(propIdx); + if (counts == nullptr) { + return false; + } + + const uint32_t numRows = element()->count; + for (uint32_t i = 0; i < numRows; i++) { + if (counts[i] != 3) { + return true; + } + } + return false; + } + + + bool PLYReader::extract_triangles(uint32_t propIdx, const float pos[], uint32_t numVerts, PLYPropertyType destType, void *dest) const + { + if (!requires_triangulation(propIdx)) { + return extract_list_property(propIdx, destType, dest); + } + + const PLYElement* elem = element(); + const PLYProperty& prop = elem->properties[propIdx]; + + const uint32_t* counts = prop.rowCount.data(); + const uint8_t* data = prop.listData.data(); + + uint8_t* to = reinterpret_cast(dest); + + bool convertSrc = !compatible_types(elem->properties[propIdx].type, PLYPropertyType::Int); + bool convertDst = !compatible_types(PLYPropertyType::Int, destType); + + size_t srcValBytes = kPLYPropertySize[uint32_t(prop.type)]; + size_t destValBytes = kPLYPropertySize[uint32_t(destType)]; + + if (convertSrc && convertDst) { + std::vector faceIndices, triIndices; + faceIndices.reserve(32); + triIndices.reserve(64); + const uint8_t* face = data; + for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) { + const uint8_t* faceEnd = face + srcValBytes * counts[faceIdx]; + faceIndices.clear(); + faceIndices.reserve(counts[faceIdx]); + for (; face < faceEnd; face += srcValBytes) { + int idx = -1; + copy_and_convert_to(&idx, face, prop.type); + faceIndices.push_back(idx); + } + + triIndices.resize((counts[faceIdx] - 2) * 3); + triangulate_polygon(counts[faceIdx], pos, numVerts, faceIndices.data(), triIndices.data()); + for (int idx : triIndices) { + copy_and_convert(to, destType, reinterpret_cast(&idx), PLYPropertyType::Int); + to += destValBytes; + } + } + } + else if (convertSrc) { + std::vector faceIndices; + faceIndices.reserve(32); + const uint8_t* face = data; + for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) { + const uint8_t* faceEnd = face + srcValBytes * counts[faceIdx]; + faceIndices.clear(); + faceIndices.reserve(counts[faceIdx]); + for (; face < faceEnd; face += srcValBytes) { + int idx = -1; + copy_and_convert_to(&idx, face, prop.type); + faceIndices.push_back(idx); + } + + uint32_t numTris = triangulate_polygon(counts[faceIdx], pos, numVerts, faceIndices.data(), reinterpret_cast(to)); + to += numTris * 3 * destValBytes; + } + } + else if (convertDst) { + std::vector triIndices; + triIndices.reserve(64); + const uint8_t* face = data; + for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) { + triIndices.resize((counts[faceIdx] - 2) * 3); + triangulate_polygon(counts[faceIdx], pos, numVerts, reinterpret_cast(face), triIndices.data()); + for (int idx : triIndices) { + copy_and_convert(to, destType, reinterpret_cast(&idx), PLYPropertyType::Int); + to += destValBytes; + } + face += srcValBytes * counts[faceIdx]; + } + } + else { + const uint8_t* face = data; + for (uint32_t faceIdx = 0; faceIdx < elem->count; faceIdx++) { + uint32_t numTris = triangulate_polygon(counts[faceIdx], pos, numVerts, reinterpret_cast(face), reinterpret_cast(to)); + face += counts[faceIdx] * srcValBytes; + to += numTris * 3 * destValBytes; + } + } + + return true; + } + + + bool PLYReader::find_pos(uint32_t propIdxs[3]) const + { + return find_properties(propIdxs, 3, "x", "y", "z"); + } + + + bool PLYReader::find_normal(uint32_t propIdxs[3]) const + { + return find_properties(propIdxs, 3, "nx", "ny", "nz"); + } + + + bool PLYReader::find_texcoord(uint32_t propIdxs[2]) const + { + return find_properties(propIdxs, 2, "u", "v") || + find_properties(propIdxs, 2, "s", "t") || + find_properties(propIdxs, 2, "texture_u", "texture_v") || + find_properties(propIdxs, 2, "texture_s", "texture_t"); + } + + + bool PLYReader::find_color(uint32_t propIdxs[3]) const + { + return find_properties(propIdxs, 3, "r", "g", "b") || + find_properties(propIdxs, 3, "red", "green", "blue"); + } + + + bool PLYReader::find_indices(uint32_t propIdxs[1]) const + { + return find_properties(propIdxs, 1, "vertex_indices") || + find_properties(propIdxs, 1, "vertex_index"); + } + + + // + // PLYReader private methods + // + + bool PLYReader::refill_buffer() + { + if (m_f == nullptr || m_atEOF) { + // Nothing left to read. + return false; + } + + if (m_pos == m_buf && m_end == m_bufEnd) { + // Can't make any more room in the buffer! + return false; + } + + // Move everything from the start of the current token onwards, to the + // start of the read buffer. + int64_t bufSize = static_cast(m_bufEnd - m_buf); + if (bufSize < kPLYReadBufferSize) { + m_buf[bufSize] = m_buf[kPLYReadBufferSize]; + m_buf[kPLYReadBufferSize] = '\0'; + m_bufEnd = m_buf + kPLYReadBufferSize; + } + size_t keep = static_cast(m_bufEnd - m_pos); + if (keep > 0 && m_pos > m_buf) { + std::memmove(m_buf, m_pos, sizeof(char) * keep); + m_bufOffset += static_cast(m_pos - m_buf); + } + m_end = m_buf + (m_end - m_pos); + m_pos = m_buf; + + // Fill the remaining space in the buffer with data from the file. + size_t fetched = fread(m_buf + keep, sizeof(char), kPLYReadBufferSize - keep, m_f) + keep; + m_atEOF = fetched < kPLYReadBufferSize; + m_bufEnd = m_buf + fetched; + + if (!m_inDataSection || m_fileType == PLYFileType::ASCII) { + return rewind_to_safe_char(); + } + return true; + } + + + bool PLYReader::rewind_to_safe_char() + { + // If it looks like a token might run past the end of this buffer, move + // the buffer end pointer back before it & rewind the file. This way the + // next refill will pick up the whole of the token. + if (!m_atEOF && (m_bufEnd[-1] == '\n' || !is_safe_buffer_end(m_bufEnd[-1]))) { + const char* safe = m_bufEnd - 2; + // If '\n' is the last char in the buffer, then a call to `next_line()` + // will move `m_pos` to point at the null terminator but won't refresh + // the buffer. It would be clearer to fix this in `next_line()` but I + // believe it'll be more performant to simply treat `\n` as an unsafe + // character here. + while (safe >= m_end && (*safe == '\n' || !is_safe_buffer_end(*safe))) { + --safe; + } + if (safe < m_end) { + // No safe places to rewind to in the whole buffer! + return false; + } + ++safe; + m_buf[kPLYReadBufferSize] = *safe; + m_bufEnd = safe; + } + m_buf[m_bufEnd - m_buf] = '\0'; + + return true; + } + + + bool PLYReader::accept() + { + m_pos = m_end; + return true; + } + + + // Advances to end of line or to next non-whitespace char. + bool PLYReader::advance() + { + m_pos = m_end; + while (true) { + while (is_whitespace(*m_pos)) { + ++m_pos; + } + if (m_pos == m_bufEnd) { + m_end = m_pos; + if (refill_buffer()) { + continue; + } + return false; + } + break; + } + m_end = m_pos; + return true; + } + + + bool PLYReader::next_line() + { + m_pos = m_end; + do { + while (*m_pos != '\n') { + if (m_pos == m_bufEnd) { + m_end = m_pos; + if (refill_buffer()) { + continue; + } + return false; + } + ++m_pos; + } + ++m_pos; // move past the newline char + m_end = m_pos; + } while (match("comment") || match("obj_info")); + + return true; + } + + + bool PLYReader::match(const char* str) + { + m_end = m_pos; + while (m_end < m_bufEnd && *str != '\0' && *m_end == *str) { + ++m_end; + ++str; + } + if (*str != '\0') { + return false; + } + return true; + } + + + bool PLYReader::which(const char* values[], uint32_t* index) + { + for (uint32_t i = 0; values[i] != nullptr; i++) { + if (keyword(values[i])) { + *index = i; + return true; + } + } + return false; + } + + + bool PLYReader::which_property_type(PLYPropertyType* type) + { + for (uint32_t i = 0; kTypeAliases[i].name != nullptr; i++) { + if (keyword(kTypeAliases[i].name)) { + *type = kTypeAliases[i].type; + return true; + } + } + return false; + } + + + bool PLYReader::keyword(const char* kw) + { + return match(kw) && !is_keyword_part(*m_end); + } + + + bool PLYReader::identifier(char* dest, size_t destLen) + { + m_end = m_pos; + if (!is_keyword_start(*m_end) || destLen == 0) { + return false; + } + do { + ++m_end; + } while (is_keyword_part(*m_end)); + + size_t len = static_cast(m_end - m_pos); + if (len >= destLen) { + return false; // identifier too large for dest! + } + std::memcpy(dest, m_pos, sizeof(char) * len); + dest[len] = '\0'; + return true; + } + + + bool PLYReader::int_literal(int* value) + { + return miniply::int_literal(m_pos, &m_end, value); + } + + + bool PLYReader::float_literal(float* value) + { + return miniply::float_literal(m_pos, &m_end, value); + } + + + bool PLYReader::double_literal(double* value) + { + return miniply::double_literal(m_pos, &m_end, value); + } + + + bool PLYReader::parse_elements() + { + m_elements.reserve(4); + while (m_valid && keyword("element")) { + parse_element(); + } + return true; + } + + + bool PLYReader::parse_element() + { + int count = 0; + + m_valid = keyword("element") && advance() && + identifier(m_tmpBuf, kPLYTempBufferSize) && advance() && + int_literal(&count) && next_line(); + if (!m_valid || count < 0) { + return false; + } + + m_elements.push_back(PLYElement()); + PLYElement& elem = m_elements.back(); + elem.name = m_tmpBuf; + elem.count = static_cast(count); + elem.properties.reserve(10); + + while (m_valid && keyword("property")) { + parse_property(elem.properties); + } + + return true; + } + + + bool PLYReader::parse_property(std::vector& properties) + { + PLYPropertyType type = PLYPropertyType::None; + PLYPropertyType countType = PLYPropertyType::None; + + m_valid = keyword("property") && advance(); + if (!m_valid) { + return false; + } + + if (keyword("list")) { + // This is a list property + m_valid = advance() && which_property_type(&countType) && advance(); + if (!m_valid) { + return false; + } + } + + m_valid = which_property_type(&type) && advance() && + identifier(m_tmpBuf, kPLYTempBufferSize) && next_line(); + if (!m_valid) { + return false; + } + + properties.push_back(PLYProperty()); + PLYProperty& prop = properties.back(); + prop.name = m_tmpBuf; + prop.type = type; + prop.countType = countType; + + return true; + } + + + bool PLYReader::load_fixed_size_element(PLYElement& elem) + { + size_t numBytes = elem.count * elem.rowStride; + + m_elementData.resize(numBytes); + + if (m_fileType == PLYFileType::ASCII) { + size_t back = 0; + + for (uint32_t row = 0; row < elem.count; row++) { + for (PLYProperty& prop : elem.properties) { + if (!load_ascii_scalar_property(prop, back)) { + m_valid = false; + return false; + } + } + next_line(); + } + } + else { + uint8_t* dst = m_elementData.data(); + uint8_t* dstEnd = dst + numBytes; + while (dst < dstEnd) { + size_t bytesAvailable = static_cast(m_bufEnd - m_pos); + if (dst + bytesAvailable > dstEnd) { + bytesAvailable = static_cast(dstEnd - dst); + } + std::memcpy(dst, m_pos, bytesAvailable); + m_pos += bytesAvailable; + m_end = m_pos; + dst += bytesAvailable; + if (!refill_buffer()) { + break; + } + } + if (dst < dstEnd) { + m_valid = false; + return false; + } + + // We assume the CPU is little endian, so if the file is big-endian we + // need to do an endianness swap on every data item in the block. + if (m_fileType == PLYFileType::BinaryBigEndian) { + uint8_t* data = m_elementData.data(); + for (uint32_t row = 0; row < elem.count; row++) { + for (PLYProperty& prop : elem.properties) { + size_t numBytes = kPLYPropertySize[uint32_t(prop.type)]; + switch (numBytes) { + case 2: + endian_swap_2(data); + break; + case 4: + endian_swap_4(data); + break; + case 8: + endian_swap_8(data); + break; + default: + break; + } + data += numBytes; + } + } + } + } + + m_elementLoaded = true; + return true; + } + + + bool PLYReader::load_variable_size_element(PLYElement& elem) + { + m_elementData.resize(elem.count * elem.rowStride); + + // Preallocate enough space for each row in the property to contain three + // items. This is based on the assumptions that (a) the most common use for + // list properties is vertex indices; and (b) most faces are triangles. + // This gives a performance boost because we won't have to grow the + // listData vector as many times during loading. + for (PLYProperty& prop : elem.properties) { + if (prop.countType != PLYPropertyType::None) { + prop.listData.reserve(elem.count * kPLYPropertySize[uint32_t(prop.type)] * 3); + } + } + + if (m_fileType == PLYFileType::Binary) { + size_t back = 0; + for (uint32_t row = 0; row < elem.count; row++) { + for (PLYProperty& prop : elem.properties) { + if (prop.countType == PLYPropertyType::None) { + m_valid = load_binary_scalar_property(prop, back); + } + else { + load_binary_list_property(prop); + } + } + } + } + else if (m_fileType == PLYFileType::ASCII) { + size_t back = 0; + for (uint32_t row = 0; row < elem.count; row++) { + for (PLYProperty& prop : elem.properties) { + if (prop.countType == PLYPropertyType::None) { + m_valid = load_ascii_scalar_property(prop, back); + } + else { + load_ascii_list_property(prop); + } + } + next_line(); + } + } + else { // m_fileType == PLYFileType::BinaryBigEndian + size_t back = 0; + for (uint32_t row = 0; row < elem.count; row++) { + for (PLYProperty& prop : elem.properties) { + if (prop.countType == PLYPropertyType::None) { + m_valid = load_binary_scalar_property_big_endian(prop, back); + } + else { + load_binary_list_property_big_endian(prop); + } + } + } + } + + m_elementLoaded = true; + return true; + } + + + bool PLYReader::load_ascii_scalar_property(PLYProperty& prop, size_t& destIndex) + { + uint8_t value[8]; + if (!ascii_value(prop.type, value)) { + return false; + } + + size_t numBytes = kPLYPropertySize[uint32_t(prop.type)]; + std::memcpy(m_elementData.data() + destIndex, value, numBytes); + destIndex += numBytes; + return true; + } + + + bool PLYReader::load_ascii_list_property(PLYProperty& prop) + { + int count = 0; + m_valid = (prop.countType < PLYPropertyType::Float) && int_literal(&count) && advance() && (count >= 0); + if (!m_valid) { + return false; + } + + const size_t numBytes = kPLYPropertySize[uint32_t(prop.type)]; + + size_t back = prop.listData.size(); + prop.rowCount.push_back(static_cast(count)); + prop.listData.resize(back + numBytes * size_t(count)); + + for (uint32_t i = 0; i < uint32_t(count); i++) { + if (!ascii_value(prop.type, prop.listData.data() + back)) { + m_valid = false; + return false; + } + back += numBytes; + } + + return true; + } + + + bool PLYReader::load_binary_scalar_property(PLYProperty& prop, size_t& destIndex) + { + size_t numBytes = kPLYPropertySize[uint32_t(prop.type)]; + if (m_pos + numBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + numBytes > m_bufEnd) { + m_valid = false; + return false; + } + } + std::memcpy(m_elementData.data() + destIndex, m_pos, numBytes); + m_pos += numBytes; + m_end = m_pos; + destIndex += numBytes; + return true; + } + + + bool PLYReader::load_binary_list_property(PLYProperty& prop) + { + size_t countBytes = kPLYPropertySize[uint32_t(prop.countType)]; + if (m_pos + countBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + countBytes > m_bufEnd) { + m_valid = false; + return false; + } + } + + int count = 0; + copy_and_convert_to(&count, reinterpret_cast(m_pos), prop.countType); + + if (count < 0) { + m_valid = false; + return false; + } + + m_pos += countBytes; + m_end = m_pos; + + const size_t listBytes = kPLYPropertySize[uint32_t(prop.type)] * uint32_t(count); + if (m_pos + listBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + listBytes > m_bufEnd) { + m_valid = false; + return false; + } + } + size_t back = prop.listData.size(); + prop.rowCount.push_back(static_cast(count)); + prop.listData.resize(back + listBytes); + std::memcpy(prop.listData.data() + back, m_pos, listBytes); + + m_pos += listBytes; + m_end = m_pos; + return true; + } + + + bool PLYReader::load_binary_scalar_property_big_endian(PLYProperty &prop, size_t &destIndex) + { + size_t startIndex = destIndex; + if (load_binary_scalar_property(prop, destIndex)) { + endian_swap(m_elementData.data() + startIndex, prop.type); + return true; + } + else { + return false; + } + } + + + bool PLYReader::load_binary_list_property_big_endian(PLYProperty &prop) + { + size_t countBytes = kPLYPropertySize[uint32_t(prop.countType)]; + if (m_pos + countBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + countBytes > m_bufEnd) { + m_valid = false; + return false; + } + } + + int count = 0; + uint8_t tmp[8]; + std::memcpy(tmp, m_pos, countBytes); + endian_swap(tmp, prop.countType); + copy_and_convert_to(&count, tmp, prop.countType); + + if (count < 0) { + m_valid = false; + return false; + } + + m_pos += countBytes; + m_end = m_pos; + + const size_t typeBytes = kPLYPropertySize[uint32_t(prop.type)]; + const size_t listBytes = typeBytes * uint32_t(count); + if (m_pos + listBytes > m_bufEnd) { + if (!refill_buffer() || m_pos + listBytes > m_bufEnd) { + m_valid = false; + return false; + } + } + size_t back = prop.listData.size(); + prop.rowCount.push_back(static_cast(count)); + prop.listData.resize(back + listBytes); + + uint8_t* list = prop.listData.data() + back; + std::memcpy(list, m_pos, listBytes); + endian_swap_array(list, prop.type, count); + + m_pos += listBytes; + m_end = m_pos; + return true; + } + + + bool PLYReader::ascii_value(PLYPropertyType propType, uint8_t value[8]) + { + int tmpInt = 0; + + switch (propType) { + case PLYPropertyType::Char: + case PLYPropertyType::UChar: + case PLYPropertyType::Short: + case PLYPropertyType::UShort: + m_valid = int_literal(&tmpInt); + break; + case PLYPropertyType::Int: + case PLYPropertyType::UInt: + m_valid = int_literal(reinterpret_cast(value)); + break; + case PLYPropertyType::Float: + m_valid = float_literal(reinterpret_cast(value)); + break; + case PLYPropertyType::Double: + default: + m_valid = double_literal(reinterpret_cast(value)); + break; + } + + if (!m_valid) { + return false; + } + advance(); + + switch (propType) { + case PLYPropertyType::Char: + reinterpret_cast(value)[0] = static_cast(tmpInt); + break; + case PLYPropertyType::UChar: + value[0] = static_cast(tmpInt); + break; + case PLYPropertyType::Short: + reinterpret_cast(value)[0] = static_cast(tmpInt); + break; + case PLYPropertyType::UShort: + reinterpret_cast(value)[0] = static_cast(tmpInt); + break; + default: + break; + } + return true; + } + + + // + // Polygon triangulation + // + + static float angle_at_vert(uint32_t idx, + const std::vector& points2D, + const std::vector& prev, + const std::vector& next) + { + Vec2 xaxis = normalize(points2D[next[idx]] - points2D[idx]); + Vec2 yaxis = Vec2{-xaxis.y, xaxis.x}; + Vec2 p2p0 = points2D[prev[idx]] - points2D[idx]; + float angle = std::atan2(dot(p2p0, yaxis), dot(p2p0, xaxis)); + if (angle <= 0.0f || angle >= kPi) { + angle = 10000.0f; + } + return angle; + } + + + uint32_t triangulate_polygon(uint32_t n, const float pos[], uint32_t numVerts, const int indices[], int dst[]) + { + if (n < 3) { + return 0; + } + else if (n == 3) { + dst[0] = indices[0]; + dst[1] = indices[1]; + dst[2] = indices[2]; + return 1; + } + else if (n == 4) { + dst[0] = indices[0]; + dst[1] = indices[1]; + dst[2] = indices[3]; + + dst[3] = indices[2]; + dst[4] = indices[3]; + dst[5] = indices[1]; + return 2; + } + + // Check that all indices for this face are in the valid range before we + // try to dereference them. + for (uint32_t i = 0; i < n; i++) { + if (indices[i] < 0 || uint32_t(indices[i]) >= numVerts) { + return 0; + } + } + + const Vec3* vpos = reinterpret_cast(pos); + + // Calculate the geometric normal of the face + Vec3 origin = vpos[indices[0]]; + Vec3 faceU = normalize(vpos[indices[1]] - origin); + Vec3 faceNormal = normalize(cross(faceU, normalize(vpos[indices[n - 1]] - origin))); + Vec3 faceV = normalize(cross(faceNormal, faceU)); + + // Project the faces points onto the plane perpendicular to the normal. + std::vector points2D(n, Vec2{0.0f, 0.0f}); + for (uint32_t i = 1; i < n; i++) { + Vec3 p = vpos[indices[i]] - origin; + points2D[i] = Vec2{dot(p, faceU), dot(p, faceV)}; + } + + std::vector next(n, 0u); + std::vector prev(n, 0u); + uint32_t first = 0; + for (uint32_t i = 0, j = n - 1; i < n; i++) { + next[j] = i; + prev[i] = j; + j = i; + } + + // Do ear clipping. + while (n > 3) { + // Find the (remaining) vertex with the sharpest angle. + uint32_t bestI = first; + float bestAngle = angle_at_vert(first, points2D, prev, next); + for (uint32_t i = next[first]; i != first; i = next[i]) { + float angle = angle_at_vert(i, points2D, prev, next); + if (angle < bestAngle) { + bestI = i; + bestAngle = angle; + } + } + + // Clip the triangle at bestI. + uint32_t nextI = next[bestI]; + uint32_t prevI = prev[bestI]; + + dst[0] = indices[bestI]; + dst[1] = indices[nextI]; + dst[2] = indices[prevI]; + dst += 3; + + if (bestI == first) { + first = nextI; + } + next[prevI] = nextI; + prev[nextI] = prevI; + --n; + } + + // Add the final triangle. + dst[0] = indices[first]; + dst[1] = indices[next[first]]; + dst[2] = indices[prev[first]]; + + return n - 2; + } + +} // namespace miniply diff --git a/src/io_ply/miniply.h b/src/io_ply/miniply.h new file mode 100644 index 00000000..d5295e58 --- /dev/null +++ b/src/io_ply/miniply.h @@ -0,0 +1,312 @@ +/* +MIT License + +Copyright (c) 2019 Vilya Harvey + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +#include +#include +#include +#include +#include + + +/// miniply - A simple and fast parser for PLY files +/// ================================================ +/// +/// For details about the PLY format see: +/// * http://paulbourke.net/dataformats/ply/ +/// * https://en.wikipedia.org/wiki/PLY_(file_format) + +namespace miniply { + + // + // Constants + // + + static constexpr uint32_t kInvalidIndex = 0xFFFFFFFFu; + + // Standard PLY element names + extern const char* kPLYVertexElement; // "vertex" + extern const char* kPLYFaceElement; // "face" + + + // + // PLY Parsing types + // + + enum class PLYFileType { + ASCII, + Binary, + BinaryBigEndian, + }; + + + enum class PLYPropertyType { + Char, + UChar, + Short, + UShort, + Int, + UInt, + Float, + Double, + + None, //!< Special value used in Element::listCountType to indicate a non-list property. + }; + + + struct PLYProperty { + std::string name; + PLYPropertyType type = PLYPropertyType::None; //!< Type of the data. Must be set to a value other than None. + PLYPropertyType countType = PLYPropertyType::None; //!< None indicates this is not a list type, otherwise it's the type for the list count. + uint32_t offset = 0; //!< Byte offset from the start of the row. + uint32_t stride = 0; + + std::vector listData; + std::vector rowCount; // Entry `i` is the number of items (*not* the number of bytes) in row `i`. + }; + + + struct PLYElement { + std::string name; //!< Name of this element. + std::vector properties; + uint32_t count = 0; //!< The number of items in this element (e.g. the number of vertices if this is the vertex element). + bool fixedSize = true; //!< `true` if there are only fixed-size properties in this element, i.e. no list properties. + uint32_t rowStride = 0; //!< The number of bytes from the start of one row to the start of the next, for this element. + + void calculate_offsets(); + + /// Returns the index for the named property in this element, or `kInvalidIndex` + /// if it can't be found. + uint32_t find_property(const char* propName) const; + + /// Return the indices for several properties in one go. Use it like this: + /// ``` + /// uint32_t indexes[3]; + /// if (elem.find_properties(indexes, 3, "foo", "bar", "baz")) { ... } + /// ``` + /// `propIdxs` is where the property indexes will be stored. `numIdxs` is + /// the number of properties we will look up. There must be exactly + /// `numIdxs` parameters after `numIdxs`; each of the is a c-style string + /// giving the name of a property. + /// + /// The return value will be true if all properties were found. If it was + /// not true, you should not use any values from propIdxs. + bool find_properties(uint32_t propIdxs[], uint32_t numIdxs, ...) const; + + /// Same as `find_properties`, for when you already have a `va_list`. This + /// is called internally by both `PLYElement::find_properties` and + /// `PLYReader::find_properties`. + bool find_properties_va(uint32_t propIdxs[], uint32_t numIdxs, va_list names) const; + + /// Call this on the element at some point before you load its data, when + /// you know that every row's list will have the same length. It will + /// replace the single variable-size property with a set of new fixed-size + /// properties: one for the list count, followed by one for each of the + /// list values. This will allow miniply to load and extract the property + /// data a lot more efficiently, giving a big performance increase. + /// + /// After you've called this, you must use PLYReader's `extract_columns` + /// method to get the data, rather than `extract_list_column`. + /// + /// The `newPropIdxs` parameter must be an array with at least `listSize` + /// entries. If the function returns true, this will have been populated + /// with the indices of the new properties that represent the list values + /// (i.e. not including the list count property, which will have the same + /// index as the old list property). + /// + /// The function returns false if the property index is invalid, or the + /// property it refers to is not a list property. In these cases it will + /// not modify anything. Otherwise it will return true. + bool convert_list_to_fixed_size(uint32_t listPropIdx, uint32_t listSize, uint32_t newPropIdxs[]); + }; + + + class PLYReader { + public: + PLYReader(const char* filename); + ~PLYReader(); + + bool valid() const; + bool has_element() const; + const PLYElement* element() const; + bool load_element(); + void next_element(); + + PLYFileType file_type() const; + int version_major() const; + int version_minor() const; + uint32_t num_elements() const; + uint32_t find_element(const char* name) const; + PLYElement* get_element(uint32_t idx); + + /// Check whether the current element has the given name. + bool element_is(const char* name) const; + + /// Number of rows in the current element. + uint32_t num_rows() const; + + /// Returns the index for the named property in the current element, or + /// `kInvalidIndex` if it can't be found. + uint32_t find_property(const char* name) const; + + /// Equivalent to calling `find_properties` on the current element. + bool find_properties(uint32_t propIdxs[], uint32_t numIdxs, ...) const; + + /// Copy the data for the specified properties into `dest`, which must be + /// an array with at least enough space to hold all of the extracted column + /// data. `propIdxs` is an array containing the indexes of the properties + /// to copy; it has `numProps` elements. + /// + /// `destType` specifies the data type for values stored in `dest`. All + /// property values will be converted to this type if necessary. + /// + /// This function does some checks up front to pick the most efficient code + /// path for extracting the data. It considers: + /// (a) whether any data conversion is required. + /// (b) whether all property values to be extracted are in contiguous + /// memory locations for any given item. + /// (c) whether the data for all rows is contiguous in memory. + /// In the best case it reduces to a single memcpy call. In the worst case + /// we must iterate over all values to be copied, applying type conversions + /// as we go. + /// + /// Note that this function does not handle list-valued properties. Use + /// `extract_list_column()` for those instead. + bool extract_properties(const uint32_t propIdxs[], uint32_t numProps, PLYPropertyType destType, void* dest) const; + + /// The same as `extract_properties`, but does not require rows in the + /// destination to be contiguous: `destStride` is the number of bytes + /// between the start of one row and the start of the next row in the + /// destination memory. + /// + /// This is useful for when your destination is an array of structs where + /// you cannot extract all of the properties with a single + /// `extract_properties` call, e.g. when not all of the struct members + /// have the same type, or when the data you're extracting is only a + /// subset of the columns in each destination row. + /// + /// This is a tiny bit slower than `extract_properties`. Wherever possible + /// you should use `extract_properties` in preference to this method. + bool extract_properties_with_stride(const uint32_t propIdxs[], uint32_t numProps, PLYPropertyType destType, void* dest, uint32_t destStride) const; + + /// Get the array of item counts for a list property. Entry `i` in this + /// array is the number of items in the `i`th list. + const uint32_t* get_list_counts(uint32_t propIdx) const; + + /// Get the sum of all item counts for a list property. This can be useful + /// to determine how big a destination array you'll need for a call to + /// `extract_list_property`. It's equivalent to summing up all the values + /// in the array returned by `get_list_counts`, but faster. + uint32_t sum_of_list_counts(uint32_t propIdx) const; + + const uint8_t* get_list_data(uint32_t propIdx) const; + bool extract_list_property(uint32_t propIdx, PLYPropertyType destType, void* dest) const; + + uint32_t num_triangles(uint32_t propIdx) const; + bool requires_triangulation(uint32_t propIdx) const; + bool extract_triangles(uint32_t propIdx, const float pos[], uint32_t numVerts, PLYPropertyType destType, void* dest) const; + + bool find_pos(uint32_t propIdxs[3]) const; + bool find_normal(uint32_t propIdxs[3]) const; + bool find_texcoord(uint32_t propIdxs[2]) const; + bool find_color(uint32_t propIdxs[3]) const; + bool find_indices(uint32_t propIdxs[1]) const; + + private: + bool refill_buffer(); + bool rewind_to_safe_char(); + bool accept(); + bool advance(); + bool next_line(); + bool match(const char* str); + bool which(const char* values[], uint32_t* index); + bool which_property_type(PLYPropertyType* type); + bool keyword(const char* kw); + bool identifier(char* dest, size_t destLen); + + template // T must be a type compatible with uint32_t. + bool typed_which(const char* values[], T* index) { + return which(values, reinterpret_cast(index)); + } + + bool int_literal(int* value); + bool float_literal(float* value); + bool double_literal(double* value); + + bool parse_elements(); + bool parse_element(); + bool parse_property(std::vector& properties); + + bool load_fixed_size_element(PLYElement& elem); + bool load_variable_size_element(PLYElement& elem); + + bool load_ascii_scalar_property(PLYProperty& prop, size_t& destIndex); + bool load_ascii_list_property(PLYProperty& prop); + bool load_binary_scalar_property(PLYProperty& prop, size_t& destIndex); + bool load_binary_list_property(PLYProperty& prop); + bool load_binary_scalar_property_big_endian(PLYProperty& prop, size_t& destIndex); + bool load_binary_list_property_big_endian(PLYProperty& prop); + + bool ascii_value(PLYPropertyType propType, uint8_t value[8]); + + private: + FILE* m_f = nullptr; + char* m_buf = nullptr; + const char* m_bufEnd = nullptr; + const char* m_pos = nullptr; + const char* m_end = nullptr; + bool m_inDataSection = false; + bool m_atEOF = false; + int64_t m_bufOffset = 0; + + bool m_valid = false; + + PLYFileType m_fileType = PLYFileType::ASCII; //!< Whether the file was ascii, binary little-endian, or binary big-endian. + int m_majorVersion = 0; + int m_minorVersion = 0; + std::vector m_elements; //!< Element descriptors for this file. + + size_t m_currentElement = 0; + bool m_elementLoaded = false; + std::vector m_elementData; + + char* m_tmpBuf = nullptr; + }; + + + /// Given a polygon with `n` vertices, where `n` > 3, triangulate it and + /// store the indices for the resulting triangles in `dst`. The `pos` + /// parameter is the array of all vertex positions for the mesh; `indices` is + /// the list of `n` indices for the polygon we're triangulating; and `dst` is + /// where we write the new indices to. + /// + /// The triangulation will always produce `n - 2` triangles, so `dst` must + /// have enough space for `3 * (n - 2)` indices. + /// + /// If `n == 3`, we simply copy the input indices to `dst`. If `n < 3`, + /// nothing gets written to dst. + /// + /// The return value is the number of triangles. + uint32_t triangulate_polygon(uint32_t n, const float pos[], uint32_t numVerts, const int indices[], int dst[]); + +} // namespace miniply diff --git a/tests/inputs/cube.ply b/tests/inputs/cube.ply new file mode 100644 index 00000000..3692d7d1 Binary files /dev/null and b/tests/inputs/cube.ply differ diff --git a/tests/mayo_tests.pro b/tests/mayo_tests.pro deleted file mode 100644 index 1d94d6fa..00000000 --- a/tests/mayo_tests.pro +++ /dev/null @@ -1,90 +0,0 @@ -TARGET = mayo_tests -TEMPLATE = app - -CONFIG += c++17 no_batch - -QT += testlib -DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x050F00 - -*msvc*:QMAKE_CXXFLAGS += /std:c++17 -*g++*:QMAKE_CXXFLAGS += -std=c++17 - -INCLUDEPATH += \ - ../src/3rdparty - -SOURCES += \ - main.cpp \ - ../src/3rdparty/fmt/src/format.cc \ - -# Base -- -HEADERS += \ - test_base.h \ - $$files(../src/base/*.h) \ - $$files(../src/io_occ/*.h) \ - -SOURCES += \ - test_base.cpp \ - $$files(../src/base/*.cpp) \ - $$files(../src/io_occ/*.cpp) \ - -# App -- -QT += widgets - -HEADERS += \ - test_app.h \ - $$files(../src/graphics/*.h) \ - $$files(../src/gui/*.h) \ - $$files(../src/io_image/*.h) \ - -SOURCES += \ - test_app.cpp \ - ../src/app/qstring_utils.cpp \ - ../src/app/recent_files.cpp \ - ../src/app/theme.cpp \ - $$files(../src/graphics/*.cpp) \ - $$files(../src/gui/*.cpp) \ - $$files(../src/io_image/*.cpp) \ - -win32 { - LIBS += -lOpengl32 -lUser32 -} - -# Copy input files -CONFIG += file_copies -COPIES += MayoInputs -MayoInputs.files = $$files(inputs/*.*) -MayoInputs.path = $$OUT_PWD/inputs - -# OpenCascade -include(../opencascade.pri) -LIBS += -lTKernel -lTKMath -lTKBRep -lTKGeomBase -lTKTopAlgo -lTKPrim -lTKMesh -lTKG3d -LIBS += -lTKXSBase -LIBS += -lTKLCAF -lTKXCAF -lTKCAF -LIBS += -lTKCDF -lTKBin -lTKBinL -lTKBinXCAF -lTKXml -lTKXmlL -lTKXmlXCAF -# -- IGES support -LIBS += -lTKIGES -lTKXDEIGES -# -- STEP support -LIBS += -lTKSTEP -lTKXDESTEP -# -- STL support -LIBS += -lTKSTL -# -- OBJ/glTF support -minOpenCascadeVersion(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 -} - -!minOpenCascadeVersion(7, 5, 0) { - SOURCES -= ../src/io_occ/io_occ_gltf_writer.cpp -} -!minOpenCascadeVersion(7, 6, 0) { - SOURCES -= ../src/io_occ/io_occ_obj_writer.cpp -} -# -- VRML support -LIBS += -lTKVRML - -# -- For TestApp -LIBS += -lTKService -lTKV3d -lTKMeshVS -lTKOpenGl diff --git a/tests/main.cpp b/tests/runtests.cpp similarity index 66% rename from tests/main.cpp rename to tests/runtests.cpp index cd21a3d4..80d48223 100644 --- a/tests/main.cpp +++ b/tests/runtests.cpp @@ -7,22 +7,28 @@ #include "test_base.h" #include "test_app.h" -#include - +#include #include #include -int main(int argc, char** argv) +namespace Mayo { + +int runTests(int argc, char* argv[]) { - // Required by TestApp - QGuiApplication guiApp(argc, argv); + QStringList args; + for (int i = 0; i < argc; ++i) { + if (std::strcmp(argv[i], "--runtests") != 0) + args.push_back(QString::fromUtf8(argv[i])); + } int retcode = 0; std::vector> vecTest; vecTest.emplace_back(new Mayo::TestBase); vecTest.emplace_back(new Mayo::TestApp); for (const std::unique_ptr& test : vecTest) - retcode += QTest::qExec(test.get(), argc, argv); + retcode += QTest::qExec(test.get(), args); return retcode; } + +} // namespace Mayo diff --git a/tests/test_app.cpp b/tests/test_app.cpp index befdd57f..79a8b2c2 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -29,13 +29,6 @@ namespace Mayo { -// Declared in app/theme.h -Theme* mayoTheme() -{ - static std::unique_ptr globalTheme(createTheme("classic")); - return globalTheme.get(); -} - void TestApp::FilePathConv_test() { const char strTestPath[] = "../as1-oc-214 - 測試文件.stp"; diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 1549928e..9783b439 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -14,6 +14,9 @@ #include "../src/base/application.h" #include "../src/base/brep_utils.h" #include "../src/base/caf_utils.h" +#include "../src/base/cpp_utils.h" +#include "../src/base/enumeration.h" +#include "../src/base/enumeration_fromenum.h" #include "../src/base/filepath.h" #include "../src/base/filepath_conv.h" #include "../src/base/geom_utils.h" @@ -49,7 +52,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -81,9 +86,8 @@ static bool operator==( void TestBase::Application_test() { auto app = Application::instance(); - auto ioSystem = Application::instance()->ioSystem(); auto fnImportInDocument = [=](const DocumentPtr& doc, const FilePath& fp) { - return ioSystem->importInDocument() + return m_ioSystem->importInDocument() .targetDocument(doc) .withFilepath(fp) .execute(); @@ -111,7 +115,7 @@ void TestBase::Application_test() auto _ = gsl::finally([=]{ app->closeDocument(doc); }); QCOMPARE(doc->entityCount(), 0); QSignalSpy sigSpy_docEntityAdded(doc.get(), &Document::entityAdded); - const bool okImport = fnImportInDocument(doc, "inputs/cube.step"); + const bool okImport = fnImportInDocument(doc, "tests/inputs/cube.step"); QVERIFY(okImport); QCOMPARE(sigSpy_docEntityAdded.count(), 1); QCOMPARE(doc->entityCount(), 1); @@ -132,11 +136,11 @@ void TestBase::Application_test() DocumentPtr doc = app->newDocument(); auto _ = gsl::finally([=]{ app->closeDocument(doc); }); bool okImport = true; - okImport = fnImportInDocument(doc, "inputs/cube.stlb"); + okImport = fnImportInDocument(doc, "tests/inputs/cube.stlb"); QVERIFY(okImport); QCOMPARE(doc->entityCount(), 1); - okImport = fnImportInDocument(doc, "inputs/cube.step"); + okImport = fnImportInDocument(doc, "tests/inputs/cube.step"); QVERIFY(okImport); QCOMPARE(doc->entityCount(), 2); @@ -149,6 +153,25 @@ void TestBase::Application_test() QCOMPARE(app->documentCount(), 0); } +void TestBase::CppUtils_toggle_test() +{ + bool v = false; + CppUtils::toggle(v); + QCOMPARE(v, true); + CppUtils::toggle(v); + QCOMPARE(v, false); +} + +void TestBase::CppUtils_safeStaticCast_test() +{ + QCOMPARE(CppUtils::safeStaticCast(42u), 42); + QCOMPARE(CppUtils::safeStaticCast(INT_MIN), INT_MIN); + QCOMPARE(CppUtils::safeStaticCast(INT_MAX), INT_MAX); + QCOMPARE(CppUtils::safeStaticCast(INT_MAX - 1), INT_MAX - 1); + QVERIFY_EXCEPTION_THROWN(CppUtils::safeStaticCast(unsigned(INT_MAX) + 1), std::overflow_error); + QVERIFY_EXCEPTION_THROWN(CppUtils::safeStaticCast(UINT_MAX), std::overflow_error); +} + void TestBase::TextId_test() { struct TextIdContext { @@ -262,27 +285,65 @@ void TestBase::PropertyQuantityValueConversion_test_data() QTest::newRow("Length(90°)") << "PropertyAngle" << Variant("90°") << Variant("1.570796rad"); } -void TestBase::IO_test() +void TestBase::IO_probeFormat_test() { QFETCH(QString, strFilePath); QFETCH(IO::Format, expectedPartFormat); - auto ioSystem = Application::instance()->ioSystem(); - QCOMPARE(ioSystem->probeFormat(strFilePath.toStdString()), expectedPartFormat); + QCOMPARE(m_ioSystem->probeFormat(strFilePath.toStdString()), expectedPartFormat); } -void TestBase::IO_test_data() +void TestBase::IO_probeFormat_test_data() { QTest::addColumn("strFilePath"); QTest::addColumn("expectedPartFormat"); - QTest::newRow("cube.step") << "inputs/cube.step" << IO::Format_STEP; - QTest::newRow("cube.iges") << "inputs/cube.iges" << IO::Format_IGES; - QTest::newRow("cube.brep") << "inputs/cube.brep" << IO::Format_OCCBREP; - QTest::newRow("bezier_curve.brep") << "inputs/mayo_bezier_curve.brep" << IO::Format_OCCBREP; - QTest::newRow("cube.stla") << "inputs/cube.stla" << IO::Format_STL; - QTest::newRow("cube.stlb") << "inputs/cube.stlb" << IO::Format_STL; - QTest::newRow("cube.obj") << "inputs/cube.obj" << IO::Format_OBJ; + QTest::newRow("cube.step") << "tests/inputs/cube.step" << IO::Format_STEP; + QTest::newRow("cube.iges") << "tests/inputs/cube.iges" << IO::Format_IGES; + QTest::newRow("cube.brep") << "tests/inputs/cube.brep" << IO::Format_OCCBREP; + QTest::newRow("bezier_curve.brep") << "tests/inputs/mayo_bezier_curve.brep" << IO::Format_OCCBREP; + QTest::newRow("cube.stla") << "tests/inputs/cube.stla" << IO::Format_STL; + QTest::newRow("cube.stlb") << "tests/inputs/cube.stlb" << IO::Format_STL; + QTest::newRow("cube.obj") << "tests/inputs/cube.obj" << IO::Format_OBJ; + QTest::newRow("cube.ply") << "tests/inputs/cube.ply" << IO::Format_PLY; +} + +void TestBase::IO_probeFormatDirect_test() +{ + char fileSample[1024]; + IO::System::FormatProbeInput input; + + auto fnSetProbeInput = [&](const FilePath& fp) { + std::memset(fileSample, 0, std::size(fileSample)); + std::ifstream ifstr; + ifstr.open(fp, std::ios::in | std::ios::binary); + ifstr.read(fileSample, std::size(fileSample)); + + input.filepath = fp; + input.contentsBegin = std::string_view(fileSample, ifstr.gcount()); + input.hintFullSize = filepathFileSize(fp); + }; + + fnSetProbeInput("tests/inputs/cube.step"); + QCOMPARE(IO::probeFormat_STEP(input), IO::Format_STEP); + + fnSetProbeInput("tests/inputs/cube.iges"); + QCOMPARE(IO::probeFormat_IGES(input), IO::Format_IGES); + + fnSetProbeInput("tests/inputs/cube.brep"); + QCOMPARE(IO::probeFormat_OCCBREP(input), IO::Format_OCCBREP); + + fnSetProbeInput("tests/inputs/cube.stla"); + QCOMPARE(IO::probeFormat_STL(input), IO::Format_STL); + + fnSetProbeInput("tests/inputs/cube.stlb"); + QCOMPARE(IO::probeFormat_STL(input), IO::Format_STL); + + fnSetProbeInput("tests/inputs/cube.obj"); + QCOMPARE(IO::probeFormat_OBJ(input), IO::Format_OBJ); + + fnSetProbeInput("tests/inputs/cube.ply"); + QCOMPARE(IO::probeFormat_PLY(input), IO::Format_PLY); } void TestBase::IO_OccStaticVariablesRollback_test() @@ -419,7 +480,7 @@ void TestBase::MeshUtils_orientation_test_data() QTest::newRow("case6") << vecPoint << Mayo::MeshUtils::Orientation::Clockwise; { - QFile file("inputs/mayo_bezier_curve.brep"); + QFile file("tests/inputs/mayo_bezier_curve.brep"); QVERIFY(file.open(QIODevice::ReadOnly)); const TopoDS_Shape shape = BRepUtils::shapeFromString(file.readAll().toStdString()); @@ -445,6 +506,38 @@ void TestBase::MeshUtils_orientation_test_data() } } +void TestBase::Enumeration_test() +{ + enum class TestBase_Enum1 { Value0, Value1, Value2, Value3, Value4 }; + using TestEnumType = TestBase_Enum1; + + Enumeration baseEnum = Enumeration::fromType(); + QVERIFY(!baseEnum.empty()); + QCOMPARE(baseEnum.size(), MetaEnum::count()); + QCOMPARE(baseEnum.items().size(), baseEnum.size()); + for (const auto& enumEntry : MetaEnum::entries()) { + QVERIFY(baseEnum.contains(enumEntry.second)); + QCOMPARE(baseEnum.findValueByName(enumEntry.second), int(enumEntry.first)); + QCOMPARE(baseEnum.findItemByName(enumEntry.second)->value, int(enumEntry.first)); + QCOMPARE(baseEnum.findItemByName(enumEntry.second)->name.key, enumEntry.second); + QCOMPARE(baseEnum.findItemByValue(enumEntry.first), baseEnum.findItemByName(enumEntry.second)); + QCOMPARE(baseEnum.findNameByValue(enumEntry.first), enumEntry.second); + QCOMPARE(baseEnum.itemAt(baseEnum.findIndexByValue(enumEntry.first)).value, int(enumEntry.first)); + QCOMPARE(baseEnum.itemAt(baseEnum.findIndexByValue(enumEntry.first)).name.key, enumEntry.second); + } + + baseEnum.chopPrefix("Value"); + for (const Enumeration::Item& item : baseEnum.items()) { + const int index = std::atoi(item.name.key.data()); + QCOMPARE(&baseEnum.itemAt(index), &item); + } + + baseEnum.changeTrContext("newTrContext"); + for (const Enumeration::Item& item : baseEnum.items()) { + QCOMPARE(item.name.trContext, "newTrContext"); + } +} + void TestBase::MetaEnum_test() { QCOMPARE(MetaEnum::name(TopAbs_VERTEX), "TopAbs_VERTEX"); @@ -613,7 +706,7 @@ void TestBase::UnitSystem_test_data() << UnitSystem::translate(schemaSI, 8 * Quantity_Meter) << UnitSystem::TranslateResult{ 8000., "mm", 1. }; QTest::newRow("50mm²") - << UnitSystem::translate(schemaSI, 0.5 * Quantity_SquaredCentimer) + << UnitSystem::translate(schemaSI, 0.5 * Quantity_SquareCentimeter) << UnitSystem::TranslateResult{ 50., "mm²", 1. }; QTest::newRow("50kg/m³") << UnitSystem::translate(schemaSI, 25 * Quantity_KilogramPerCubicMeter) @@ -730,10 +823,16 @@ void TestBase::LibTree_test() void TestBase::initTestCase() { - IO::System* ioSystem = Application::instance()->ioSystem(); - ioSystem->addFactoryReader(std::make_unique()); - ioSystem->addFactoryWriter(std::make_unique()); - IO::addPredefinedFormatProbes(ioSystem); + m_ioSystem = new IO::System; + m_ioSystem->addFactoryReader(std::make_unique()); + m_ioSystem->addFactoryWriter(std::make_unique()); + IO::addPredefinedFormatProbes(m_ioSystem); +} + +void TestBase::cleanupTestCase() +{ + delete m_ioSystem; + m_ioSystem = nullptr; } } // namespace Mayo diff --git a/tests/test_base.h b/tests/test_base.h index 7a911a33..79db1658 100644 --- a/tests/test_base.h +++ b/tests/test_base.h @@ -11,11 +11,16 @@ namespace Mayo { +namespace IO { class System; } + class TestBase : public QObject { Q_OBJECT private slots: void Application_test(); + void CppUtils_toggle_test(); + void CppUtils_safeStaticCast_test(); + void TextId_test(); void FilePath_test(); @@ -25,8 +30,9 @@ private slots: void PropertyQuantityValueConversion_test(); void PropertyQuantityValueConversion_test_data(); - void IO_test(); - void IO_test_data(); + void IO_probeFormat_test(); + void IO_probeFormat_test_data(); + void IO_probeFormatDirect_test(); void IO_OccStaticVariablesRollback_test(); void IO_OccStaticVariablesRollback_test_data(); @@ -39,6 +45,7 @@ private slots: void MeshUtils_orientation_test(); void MeshUtils_orientation_test_data(); + void Enumeration_test(); void MetaEnum_test(); void Quantity_test(); @@ -55,6 +62,10 @@ private slots: void LibTree_test(); void initTestCase(); + void cleanupTestCase(); + +private: + IO::System* m_ioSystem = nullptr; }; } // namespace Mayo diff --git a/tests/tests.pri b/tests/tests.pri new file mode 100644 index 00000000..262932c8 --- /dev/null +++ b/tests/tests.pri @@ -0,0 +1,16 @@ +QT += testlib + +HEADERS += \ + $$PWD/test_app.h \ + $$PWD/test_base.h \ + +SOURCES += \ + $$PWD/runtests.cpp \ + $$PWD/test_app.cpp \ + $$PWD/test_base.cpp \ + +# Copy input files +CONFIG += file_copies +COPIES += MayoTestsInputs +MayoTestsInputs.files = $$files($$PWD/inputs/*.*) +MayoTestsInputs.path = $$OUT_PWD/tests/inputs diff --git a/version.pri b/version.pri index 068b2cd1..26c185c4 100644 --- a/version.pri +++ b/version.pri @@ -9,8 +9,8 @@ defined(HAVE_GIT, var) { } MAYO_VERSION_MAJ = 0 -MAYO_VERSION_MIN = 5 -MAYO_VERSION_PAT = 2 +MAYO_VERSION_MIN = 6 +MAYO_VERSION_PAT = 0 VERSION = $${MAYO_VERSION_MAJ}.$${MAYO_VERSION_MIN}.$${MAYO_VERSION_PAT}.$${MAYO_VERSION_REVNUM} MAYO_VERSION = $${VERSION}-$$MAYO_VERSION_COMMIT @@ -25,10 +25,8 @@ equals(QT_ARCH, i386) { QMAKE_TARGET_PRODUCT = Mayo QMAKE_TARGET_COMPANY = Fougue -# Generate version files -QMAKE_SUBSTITUTES += \ - $$PWD/installer/version.iss.in \ - $$PWD/src/app/version.h.in +# Generate version file +QMAKE_SUBSTITUTES += $$PWD/src/app/version.h.in INCLUDEPATH += $$OUT_PWD/src/app # To allow inclusion as "version.h" from source code OTHER_FILES += $$PWD/src/app/version.h.in