-# 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
-
-
+
+
+
+
Mayo the opensource 3D CAD viewer and converter9>
+
+
+
-
+## :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
+
- Very Coarse
+ Very Coarse
+
- Coarse
+ Coarse
+
- Normal
+ Normal
+
- Precise
+ Precise
+
- Very Precise
+ Very Precise
+
- User Defined
+ User DefinedMayo::AppModule
-
+ English
-
+ French
-
- System
+ System
-
- Units
+ Units
-
- Count Of Decimals
+ Count Of Decimals
-
- Schema
+ Schema
-
- Application
+ Application
-
- Language
+ Language
-
- Recent Files
+ Recent Files
-
- Last Open Folder
+ Last Open Folder
-
- Last Selected Format Filter
+ Last Selected Format Filter
-
- Link With Document Selector
+ Link With Document Selector
-
- Graphics
+ Graphics
-
- Show Origin Trihedron By Default
+ Show Origin Trihedron By Default
-
- Clip planes
+ Clip planes
-
- Capping
+ Capping
-
- Capping Hatch
+ Capping Hatch
-
- Mesh Defaults
+ Mesh Defaults
-
- Quality
+ Quality
-
- Chordal Deflection
+ Chordal Deflection
-
- Angular Deflection
+ Angular Deflection
-
- Relative
+ Relative
-
- Instant Zoom Factor
+ Instant Zoom Factor
-
- Color
+ Color
-
- Edge Color
+ Edge Color
-
- Material
+ Material
-
- Show Edges
+ Show Edges
-
- Show Nodes
+ Show Nodes
-
+
+ BRep Meshing
+
+
+
+ Import
+
+
+
+ Export
+
+
+
+ Very Coarse
+
+
+
+ Coarse
+
+
+
+ Normal
+
+
+
+ Precise
+
+
+
+ Very Precise
+
+
+
+ User Defined
+
+
+
+ Mayo::AppModuleProperties
+
+
+
+ Language
+
+
+
+
+ System
+
+
+
+
+ Application
+
+
+ BRep Meshing
-
+
+
+ Graphics
+
+
+
+
+ Units
+
+
+
+
+ Clip planes
+
+
+
+
+ Mesh Defaults
+
+
+
+
+ Import
+
+
+
+
+ Export
+
+
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
-
+
+
+
+
+
+
-
+
-
+
-
-
- Import
+
+
+ Count Of Decimals
-
-
- Export
+
+
+ Schema
-
-
- Very Coarse
+
+
+ Recent Files
-
-
- Coarse
+
+
+ Last Open Folder
-
-
- Normal
+
+
+ Last Selected Format Filter
-
-
- Precise
+
+
+ Link With Document Selector
-
-
- Very Precise
+
+
+ Quality
-
-
- User Defined
+
+
+ Chordal Deflection
+
+
+
+
+ Angular Deflection
+
+
+
+
+ Relative
+
+
+
+
+ View Navigation Style
+
+
+
+
+ Show Origin Trihedron By Default
+
+
+
+
+ Instant Zoom Factor
+
+
+
+
+ Capping
+
+
+
+
+ Capping Hatch
+
+
+
+
+ Color
+
+
+
+
+ Edge Color
+
+
+
+
+ Material
+
+
+
+
+ Show Edges
+
+
+
+
+ Show NodesMayo::Application
-
+
-
+
+
+ Mayo::BRepMeasureError
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Mayo::CliExport
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Mayo::DialogAbout
@@ -563,64 +768,147 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho
- Mayo::GraphicsMeshObjectDriver_ObjectProperties
+ Mayo::DocumentPropertyGroup
-
+
+
+ File Path
+
+
+
+
+ File Size
+
+
+
+
+ Created
+
+
+
+
+ Modified
+
+
+
+
+ Owner
+
+
+
+
+ Count Of Entities
+
+
+
+ Mayo::GraphicsMeshObjectDriver
+
+
+
+ [Mesh] Wireframe
+
+
+
+
+ [Mesh] Shaded
+
+
+
+
+ [Mesh] Shrink
+
+
+ Color
-
+ Edge Color
-
+ Show Edges
-
+ Show Nodes
+
+ Mayo::GraphicsMeshObjectDriver_ObjectProperties
+
+
+ Color
+
+
+
+ Edge Color
+
+
+
+ Show Edges
+
+
+
+ Show Nodes
+
+Mayo::GraphicsObjectDriver
-
- [Shape] Wireframe
+ [Shape] Wireframe
-
- [Shape] Hidden Line Removal
+ [Shape] Hidden Line Removal
-
- [Shape] Shaded
+ [Shape] Shaded
-
- [Shape] Shaded With Edges
+ [Shape] Shaded With Edges
-
- [Mesh] Wireframe
+ [Mesh] Wireframe
-
- [Mesh] Shaded
+ [Mesh] Shaded
-
- [Mesh] Shrink
+ [Mesh] Shrink
+
+
+
+ Mayo::GraphicsShapeObjectDriver
+
+
+
+ [Shape] Wireframe
+
+
+
+
+ [Shape] Hidden Line Removal
+
+
+
+
+ [Shape] Shaded
+
+
+
+
+ [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
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+ 64bit Float Format
-
+ 64bit Float Precision
-
+ Create ZIP Archive
-
+ ZIP Entry Filename
-
+ Use ZIP64 extensions
@@ -762,52 +1050,52 @@ Only applicable if option `{}` is on
Mayo::IO::ImageWriterI18N
-
+
-
+
-
+
-
+ Width
-
+ Height
-
+ Background Color
-
+ Camera Orientation
-
+ Camera Projection
-
+
-
+
@@ -840,65 +1128,6 @@ Only applicable if option `{}` is on
System length units to convert into while reading files
-
- Mayo::IO::OccCommon
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Mayo::IO::OccGltfReader::Properties
@@ -1415,86 +1644,122 @@ It can be disabled in order to minimize the size of the resulting file.
Target Format
-
- Text
+ Text
-
- Binary
+ Binary
+
+
+
+ Mayo::IO::OccVrmlWriter::Properties
+
+
+
+ Shape Representation
+
+
+
+ Mayo::IO::PlyWriterI18N
+
+
+
+
+
+
+
+
+ Target Format
+
+
+
+
+ Write Colors
+
+
+
+
+ Default Color
+
+
+
+
+ Comment
+
+
+
+
+
-
-
- Mayo::IO::OccVrmlWriter::Properties
-
-
- Shape Representation
+
+
+ Mayo::IO::System
-
+
-
+
-
+
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
+
-
+
-
+
@@ -1502,108 +1767,99 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::Main
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
-
+ File Path
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
-
+
@@ -1699,7 +1955,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+ Import
@@ -1730,7 +1986,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
@@ -1887,105 +2143,160 @@ It can be disabled in order to minimize the size of the resulting file.
-
+ %1 is the format identifier and %2 is the file filters string
-
+
-
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
+
-
+
-
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
+
+ Mayo::MeasureDisplayI18N
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Area
+
+Mayo::Mesh_DocumentTreeNodeProperties
@@ -2020,33 +2331,33 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::PropertyItemDelegate
-
+
-
+
-
+
-
+
-
-
+
+
-
+
@@ -2054,56 +2365,56 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::QStringUtils
-
-
+
+
-
+
-
-
+
+
-
+
-
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
@@ -2193,57 +2504,62 @@ Last modified: %3
Mayo::WidgetGuiDocument
-
+
-
+
-
+
-
+
+
+
+
+
+
-
+
-
+
-
+
-
+
-
+
-
+
-
+
@@ -2276,28 +2592,28 @@ Select files to load and open as distinct documents
-
+
-
+
-
-
+
+
-
+
-
+
- Mayo::WidgetModelTree
+ Mayo::WidgetMeasure
-
+
-
-
+
+
-
-
+
+
-
-
- Mayo::WidgetModelTreeBuilder_Xde
-
-
- Name Format Of Assembly Instances
+
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
- Mayo::WidgetPropertiesEditor
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- Mayo::WidgetShapeSelector
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+ Mayo::WidgetModelTree
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+ Mayo::WidgetModelTreeBuilder_Xde
+
+
+
+ Name Format Of Assembly Instances
+
-
-
+
+
+
+
+ Mayo::WidgetPropertiesEditor
-
-
+
+
-
-
+
+
-
-
+
+
@@ -2533,15 +2919,76 @@ Read: %5
Sub
+
+ OccCommon
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+OccStlWriter::Properties
+
- Text
+ Text
+
- Binary
+ Binary
@@ -2715,4 +3162,22 @@ Read: %5
+
+ WidgetModelTreeBuilder_Xde
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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
+
- Très grossière
+ Très grossière
+
- Grossière
+ Grossière
+
- Normale
+ Normale
+
- Précise
+ Précise
+
- Très précise
+ Très précise
+
- Custom
+ CustomMayo::AppModule
-
+ Anglais
-
+ Français
-
- Système
+ Système
-
- Unités
+ Unités
-
- Nombre de décimales
+ Nombre de décimales
-
- Schéma
+ Schéma
-
- Application
+ Application
-
- Langue
+ Langue
-
- Fichiers récents
+ Fichiers récents
-
- Dernier répertoire ouvert
+ Dernier répertoire ouvert
-
- Dernier filtre de format sélectionné
+ Dernier filtre de format sélectionné
-
- Lier au sélecteur de documents
+ Lier au sélecteur de documents
-
- Graphisme
+ Graphisme
-
- Afficher le trihèdre Origine par défaut
+ Afficher le trihèdre Origine par défaut
-
- Plans de coupe
+ Plans de coupe
-
- Bouchage
+ Bouchage
-
- Bouchages avec hachures
+ Bouchages avec hachures
-
- Maillage par défauts
+ Maillage par défauts
-
- Qualité
+ Qualité
-
- Déflection chordale
+ Déflection chordale
-
- Déflection angulaire
+ Déflection angulaire
-
- Relatif
+ Relatif
-
- Coefficient du zoom instantané
+ Coefficient du zoom instantané
-
- Couleur
+ Couleur
-
- Couleur des arêtes
+ Couleur des arêtes
-
- Matériau
+ Matériau
-
- Afficher les arêtes
+ Afficher les arêtes
-
- Afficher les nœuds
+ Afficher les nœuds
-
- Maillage BRep
+ Maillage BRep
-
- 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
-
- 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
-
- 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
-
- 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
-
- 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
-
- 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` × `TailleArête`. La déflection utilisée pour les faces sera la déflection maximale de ses arêtes.
-
- 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
-
- Activer le bouchage des graphismes actuellement coupés
+ Activer le bouchage des graphismes actuellement coupés
-
- 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
-
- Export
+ Export
-
- Très grossière
+ Très grossière
-
- Grossière
+ Grossière
-
- Normale
+ Normale
-
- Précise
+ Précise
-
- Très précise
+ Très précise
-
- Custom
+ Custom
+
+
+
+ Mayo::AppModuleProperties
+
+
+
+ Langue
+
+
+
+
+ Système
+
+
+
+
+ Application
+
+
+
+
+ Maillage BRep
+
+
+
+
+ Graphisme
+
+
+
+
+ Unités
+
+
+
+
+ Plans de coupe
+
+
+
+
+ Maillage par défauts
+
+
+
+
+ Import
+
+
+
+
+ Export
+
+
+
+
+ Langage de l'application. Tout changement sera effectif après redémarrage de l'application
+
+
+
+
+ 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
+
+
+
+
+ Contrôle la précision du maillage calculé à partir de la forme BRep
+
+
+
+
+ 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 angulaire limite l'angle entre les segments successifs d'une polyligne
+
+
+
+
+ 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` × `TailleArête`. La déflection utilisée pour les faces sera la déflection maximale de ses arêtes.
+
+
+
+
+ Configuration des raccourcis pour manipuler la vue 3D, permet d'imiter les autres application CAO
+
+
+
+
+ Montrer/cacher par défaut le trièdre positionné à l'orgine "monde". N'affecte pas la vue 3D des documents actuellement ouverts
+
+
+
+
+ Activer le bouchage des graphismes actuellement coupés
+
+
+
+
+ Activer le hachage texturé pour le bouchage des graphismes actuellement coupés
+
+
+
+
+ Nombre de décimales
+
+
+
+
+ Schéma
+
+
+
+
+ Fichiers récents
+
+
+
+
+ Dernier répertoire ouvert
+
+
+
+
+ Dernier filtre de format sélectionné
+
+
+
+
+ Lier au sélecteur de documents
+
+
+
+
+ Qualité
+
+
+
+
+ Déflection chordale
+
+
+
+
+ Déflection angulaire
+
+
+
+
+ Relatif
+
+
+
+
+ Style de navigation de la vue
+
+
+
+
+ Afficher le trihèdre Origine par défaut
+
+
+
+
+ Coefficient du zoom instantané
+
+
+
+
+ Bouchage
+
+
+
+
+ Bouchages avec hachures
+
+
+
+
+ Couleur
+
+
+
+
+ Couleur des arêtes
+
+
+
+
+ Matériau
+
+
+
+
+ Afficher les arêtes
+
+
+
+
+ Afficher les nœudsMayo::Application
-
+
-
+
@@ -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
+
+
+
+ L'entité doit être un sommet
+
+
+
+
+ L'entité doit être une arête circulaire
+
+
+
+
+ L'entité doit une forme BREP
+
+
+
+
+ Échec du calcul de la distance minimum
+
+
+
+
+ Toutes les entités doivent être des arêtes
+
+
+
+
+ L'entité doit une arête linéaire
+
+
+
+
+ Toutes les entités doivent être des faces
+
+
+
+
+ Les entités ne doivent pas être parallèles
+
+
+
+
+ Erreur inconnue
+
+
+
+ Mayo::CliExport
+
+
+
+ Maillage des formes BRep
+
+
+
+
+ Importé
+
+
+
+
+ Export de {} terminé
+
+
+
+
+ Import en cours ...
+
+
+
+
+ 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
+
+
+
+ Chemin
+
+
+
+
+ Taille
+
+
+
+
+ Créé
+
+
+
+
+ Modifié
+
+
+
+
+ Propriétaire
+
+
+
+
+ Nombre d'entités
+
+
+
+ Mayo::GraphicsMeshObjectDriver
+
+
+
+ [Maillage] Filaire
+
+
+
+
+ [Maillage] Ombré
+
+
+
+
+ [Maillage] Rétréci
+
-
+ Couleur
-
+ Couleur des arêtes
-
+ Montrer les arêtes
-
+ Montrer les nœuds
+
+ Mayo::GraphicsMeshObjectDriver_ObjectProperties
+
+
+ Couleur
+
+
+
+ Couleur des arêtes
+
+
+
+ Montrer les arêtes
+
+
+
+ Montrer les nœuds
+
+Mayo::GraphicsObjectDriver
-
- [Forme] Filaire
+ [Forme] Filaire
-
- [Forme] Suppression des arêtes cachées
+ [Forme] Suppression des arêtes cachées
-
- [Forme] Ombré
+ [Forme] Ombré
-
- [Forme] Ombré avec arêtes
+ [Forme] Ombré avec arêtes
-
- [Maillage] Filaire
+ [Maillage] Filaire
-
- [Maillage] Ombré
+ [Maillage] Ombré
-
- [Maillage] Rétréci
+ [Maillage] Rétréci
+
+
+
+ Mayo::GraphicsShapeObjectDriver
+
+
+
+ [Forme] Filaire
+
+
+
+
+ [Forme] Suppression des arêtes cachées
+
+
+
+
+ [Forme] Ombré
+
+
+
+
+ [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 à utiliser lors de l'écriture des valeurs de type `double`en chaînes de caractères
-
+ Décimal à virgule flottante (ex: 392,65)
-
+ Notation scientifique (ex: 3,9265E+2)
-
+ Utiliser la notation la plus compacte : décimale ou scientifique
-
+ Nombre maximal de chiffres significatifs lors de l'écriture de valeurs de type `double`
-
+ Écrire le document AMF dans une archive ZIP contenant une entrée de fichier
-
+ Nom de l'entrée du fichier AMF dans l'archive ZIP.
Seulement applicable si l'option `{}` est activée
-
+ 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
-
+ Format nombres flottants 64bit
-
+ Précision nombres flottants 64bit
-
+ Créer une archive ZIP
-
+ Nom du fichier de l'entrée ZIP
-
+ Utiliser les extensions ZIP64
@@ -794,52 +1122,52 @@ Seulement applicable si l'option `%1` est activée
Mayo::IO::ImageWriterI18N
-
+ Largeur de l'image en pixels
-
+ Hauteur de l'image en pixels
-
+ Orientation de la caméra selon la convention Z-up exprimée en tant que vecteur unitaire
-
+ Largeur
-
+ Hauteur
-
+ Couleur de l'arrière plan
-
+ Orientation de la caméra
-
+ Projection de la caméra
-
+ Aucun élément transféré
-
+ 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
-
-
- Indéfini
+ Indéfini
-
- +Zup
+ +Zup
-
- +Yup
+ +Yup
-
- Micromètre
+ Micromètre
-
- Millimètre
+ Millimètre
-
- Centimètre
+ Centimètre
-
- Mètre
+ Mètre
-
- Kilomètre
+ Kilomètre
-
- Pouce
+ Pouce
-
- Pied
+ Pied
-
- Mile
+ Mile
@@ -1095,7 +1411,7 @@ Applicable seulement si l'option `{}` est cochée
-
+ 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
-
- Texte
+ Texte
-
- 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
+
+
+
+ Texte qui apparaîtra dans l'en-tête
+
+
+
+
+ Format cible
+
+
+
+
+ Écrire la couleur des sommets
+
+
+
+
+ Couleur par défaut
+
+
+
+
+ Commentaire
+
+
+
+
+ Impossible d'ouvrir le fichier
+
+
+
+
+ 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
-
+ Lecture fichier
-
+ Format inconnu
-
+ Erreur pendant l'import de '{}'
{}
-
+ Aucun lecteur compatible
-
+ Problème de lecture fichier
-
+ Transfert du fichier
-
-
+
+ Problème de transfert fichier
-
+ Erreur pendant l'export de '{}'
@@ -1556,22 +1908,22 @@ It can be disabled in order to minimize the size of the resulting file.
%2
-
+ Aucun writer compatible
-
+ Transfert
-
+ Écriture
-
+ 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, une visionneuse 3D en code libre basée surQt5/OpenCascade
+ Mayo, une visionneuse 3D en code libre basée surQt5/OpenCascade
-
+ Thème de l'IHM (classic|dark)
-
+ nom
-
+
+
+ Écrit les messages de log dans un fichier de sortie
+
+
+
+
+ Ne pas filtrer les messages de debug dans la version "release"
+
+
+ Désactiver l'indicateur de progression dans la sortie console (mode CLI seulement)
-
+ files
-
+ Fichiers à ouvrir au démarrage, optionnel
-
+ [fichiers ...]
-
+
+
+ Exécuter les tests unitaires et quitter l'application
+
+
+ Le fichier de configuration OpenCascade n'existe pas ou non lisible [chemin=%1]
-
+ Le fichier de configuration OpenCascade n'a pu être chargé par QSettings [chemin=%1]
-
+ Échec chargement du fichier de traductions [chemin=%1]
-
- Export de {} terminé
+ Export de {} terminé
-
- Export de {} en cours ...
+ Export de {} en cours ...
-
+ É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.
-
+ Fichier de configuration (format INI) à charger au démarrage
-
-
+
+
+ Mayo le visualiseur et convertisseur 3D pour la CAO
+
+
+
+
+
-
+ 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 ...)
-
- Maillage des formes BRep
+ Maillage des formes BRep
-
- Importé
+ Importé
-
- Import en cours ...
+ Import en cours ...
@@ -1689,12 +2056,12 @@ It can be disabled in order to minimize the size of the resulting file.
Export de %1 en cours ...
-
+ Auncun fichier en entrée -> aucun export
-
+ 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.
-
+ Importer
@@ -1829,7 +2196,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
@@ -1857,7 +2224,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+
@@ -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 is the format identifier and %2 is the file filters string
- %1 fichiers (%2)
+ %1 fichiers (%2)
-
+ Tous les fichiers (*.*)
-
+ Selectionner fichier pièce
-
+ Avertissement
-
-
+
+ Erreur
-
+ À propos %1
-
+ Anonyme%1
-
-
+
+ Maillage des formes BRep
-
-
+
+ Durée import: {}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
-
+ 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
-
+
+ Données
-
+ Graphismes
-
+ Fermer %1
-
+ Fermer
-
+ Tout fermer sauf %1
-
+ Tout fermer sauf document courant
-
+
-
+ Vider le menu
+
+ Mayo::MeasureDisplayI18N
+
+
+
+ Total
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Diamètre: {0}{1}
+
+
+
+
+
+
+
+
+
+ Distance Min: {0}{1}<br>Point1: {2}<br>Point2: {3}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Longueur
+
+
+
+
+ Aire
+
+Mayo::Mesh_DocumentTreeNodeProperties
@@ -2127,33 +2549,33 @@ It can be disabled in order to minimize the size of the resulting file.
Mayo::PropertyItemDelegate
-
+ %1j
-
+ %1h
-
+ %1min
-
+ %1s
-
-
+
+ %1%2
-
+ 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; %4]
-
-
+
+ %1%2
-
+ o
-
+ Ko
-
-
+
+ Mo
-
-
+
+ Oui
-
-
+
+ Non
-
+ Partiellement
@@ -2310,7 +2732,7 @@ It can be disabled in order to minimize the size of the resulting file.
-
+ Form
@@ -2335,57 +2757,62 @@ Modifié le: %3 {1
Mayo::WidgetGuiDocument
-
+ Adapter à tout
-
+ Éditer les plans de coupe
-
+ Éclater l'assemblage
-
+
+
+ Mesures
+
+
+ Isométrique
-
+ Arrière
-
+ Devant
-
+ Gauche
-
+ Droit
-
+ Haut
-
+ Bas
-
+ <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
-
+ aujourd'hui %1
-
+ hier %1
-
-
+
+ %1 %2
-
+ %1 jours %2
-
+
+
+ Mayo::WidgetMeasure
+
+
+
+ Form
+
+
+
+
+ Unité aire
+
+
+
+
+ Mesure
+
+
+
+
+ Millimètre(mm)
+
+
+
+
+ Centimètre(cm)
+
+
+
+
+ Mètre(m)
+
+
+
+
+ Pouce(in)
+
+
+
+
+ Pied(ft)
+
+
+
+
+
+
+
+
+
+ Degré(°)
+
+
+
+
+
+
+
+
+
+ Position sommet
+
+
+
+
+ Centre cercle
+
+
+
+
+ Diamètre cercle
+
+
+
+
+ Distance min
+
+
+
+
+ Longueur
+
+
+
+
+ Millimètre carré(mm²)
+
+
+
+
+ Centimètre carré(cm²)
+
+
+
+
+ Mètre carré(m²)
+
+
+
+
+ Pouce carré(in²)
+
+
+
+
+ Pied carré(ft²)
+
+
+
+
+ Yard carré(yd²)
+
+
+
+
+
+
+
+
+
+ Aire surface
+
+
+
+
+ Unité longueur
+
+
+
+ Millimètre
+
+
+
+ Centimètre
+
+
+
+ Mètre
+
+
+
+ Pouce
+
+
+
+ Pied
+
+
+
+
+ Unité angle
+
+
+
+
+ Sélectionner les entités à mesurer
+
+Mayo::WidgetModelTree
@@ -2484,12 +3069,12 @@ Lu: %5
Mayo::WidgetModelTreeBuilder_Xde
-
+ Format des noms d'instance
-
+ Montrer {}
@@ -2498,19 +3083,16 @@ Lu: %5
Montrer %1
-
- Instance
+ Instance
-
- Produit
+ Produit
-
- Les Deux
+ Les Deux
@@ -2531,59 +3113,6 @@ Lu: %5
Valeur
-
- Mayo::WidgetShapeSelector
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-Mayo::XCaf_DocumentTreeNodeProperties
@@ -2694,59 +3223,73 @@ Lu: %5
OccCommon
+
+
- Indéfini
+ Indéfini
+
- +Zup
+ +Zup
+
- +Yup
+ +Yup
+
- Micromètre
+ Micromètre
+
- Millimètre
+ Millimètre
+
- Centimètre
+ Centimètre
+
- Mètre
+ Mètre
+
- Kilomètre
+ Kilomètre
+
- Pouce
+ Pouce
+
- Pied
+ Pied
+
- Mile
+ MileOccStlWriter::Properties
+
- Texte
+ Texte
+
- Binaire
+ Binaire
@@ -2923,16 +3466,19 @@ Lu: %5
WidgetModelTreeBuilder_Xde
+
- Instance
+ Instance
+
- Produit
+ Produit
+
- 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.svgimages/themes/classic/reload.svgimages/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