diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 22a2411f..87db274f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,8 +7,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest] - #os: [ubuntu-latest, macos-latest] + os: [ubuntu-latest, macos-latest] runs-on: ${{matrix.os}} @@ -45,10 +44,9 @@ jobs: if: startsWith(matrix.os, 'macos') run: | brew install opencascade - GH_CASCADE_BASE_DIR=`brew --cellar opencascade` - GH_CASCADE_VERSION=`brew info opencascade | grep -E --only-matching --max-count=1 "[0-9]\.[0-9]\.[0-9]"` - echo "GH_CASCADE_INC_DIR=$GH_CASCADE_BASE_DIR/$GH_CASCADE_VERSION/include/opencascade" >> $GITHUB_ENV - echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_BASE_DIR/$GH_CASCADE_VERSION/lib" >> $GITHUB_ENV + GH_CASCADE_BASE_DIR=`brew info opencascade | grep -E -i --only-matching --max-count=1 "^(/[a-z\.\-_0-9]+)+"` + echo "GH_CASCADE_INC_DIR=$GH_CASCADE_BASE_DIR/include/opencascade" >> $GITHUB_ENV + echo "GH_CASCADE_LIB_DIR=$GH_CASCADE_BASE_DIR/lib" >> $GITHUB_ENV - name: Get count of CPU cores uses: SimenB/github-actions-cpu-cores@v1 @@ -62,6 +60,8 @@ jobs: run: | echo CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} echo CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} + [ ! -d $CASCADE_INC_DIR ] && echo "ERROR: OpenCascade include dir doesn't exist" + [ ! -d $CASCADE_LIB_DIR ] && echo "ERROR: OpenCascade lib dir doesn't exist" qmake ../source CASCADE_INC_DIR=${{env.GH_CASCADE_INC_DIR}} CASCADE_LIB_DIR=${{env.GH_CASCADE_LIB_DIR}} CONFIG+=withtests - name: Build @@ -75,8 +75,14 @@ jobs: env: DISPLAY: :0 run: | + # Install french locale for testing case + echo "fr_FR ISO-8859-1" | sudo tee -a /etc/locale.gen + sudo locale-gen + localectl list-locales + # Start X11 display server Xvfb $DISPLAY -screen 0 1280x1024x24 & sleep 5s + # Run tests ./mayo --runtests - name: Execute Unit Tests[macOS] diff --git a/README.md b/README.md index db904fbe..e8051a4e 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@
[![CI](https://github.com/fougue/mayo/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/fougue/mayo/actions/workflows/ci.yml) -[![Build status](https://ci.appveyor.com/api/projects/status/6d1w0d6gw28npxpf?svg=true)](https://ci.appveyor.com/project/HuguesDelorme/mayo) +[![Build status](https://ci.appveyor.com/api/projects/status/6d1w0d6gw28npxpf/branch/develop?svg=true)](https://ci.appveyor.com/project/HuguesDelorme/mayo) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/d51f8ca6fea34886b8308ff0246172ce)](https://www.codacy.com/gh/fougue/mayo/dashboard?utm_source=github.com&utm_medium=referral&utm_content=fougue/mayo&utm_campaign=Badge_Grade) [![Downloads](https://img.shields.io/github/downloads/fougue/mayo/total.svg)](https://github.com/fougue/mayo/releases) [![License](https://img.shields.io/badge/license-BSD%202--clause-blue.svg)](https://github.com/fougue/mayo/blob/develop/LICENSE.txt) -[![Version](https://img.shields.io/badge/version-v0.6.0-blue.svg?style=flat)](https://github.com/fougue/mayo/releases) +[![Version](https://img.shields.io/badge/version-v0.7.0-blue.svg?style=flat)](https://github.com/fougue/mayo/releases)
@@ -30,6 +30,10 @@ 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) +For more details have a look at this fine review [Introducing Mayo](https://librearts.org/2023/01/introducing-mayo-free-cad-files-viewer) by Libre Arts
+There's also a complete [video](https://www.youtube.com/watch?v=qg6IamnlfxE&ab_channel=LibreArts) on YouTube + + ## :zap: Features - **3D clip planes** with configurable capping @@ -56,10 +60,11 @@ 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 +VRML | :white_check_mark: | :white_check_mark: | v2.0 UTF8 STL | :white_check_mark: | :white_check_mark: | ASCII/binary AMF | :x: | :white_check_mark: | v1.2 Text/ZIP PLY | :white_check_mark: | :white_check_mark: | ASCII/binary +OFF | :white_check_mark: | :white_check_mark: | Image | :x: | :white_check_mark: | PNG, JPEG, ... See also this dedicated [wikipage](https://github.com/fougue/mayo/wiki/Supported-formats) for more details @@ -86,12 +91,20 @@ Mayo supports also multiple 3D viewer navigation styles to mimic common CAD appl ## :clapper: Gallery - - +_Easy to use command-line utility for batch conversion of CAD files_ + + +_Import of glTF file with textures_ +_Import of STEP file with many parts_ +_Import of PLY file defining point cloud_ + + +_Options dialog with import/export configuration per CAD format_ +_Home page with quick access to recent files_ diff --git a/appveyor.yml b/appveyor.yml index 18fae026..4f7157bf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 0.6_build{build} +version: 0.7_build{build} image: Visual Studio 2017 platform: x64 @@ -6,24 +6,28 @@ configuration: Release clone_folder: c:\projects\mayo -branches: - only: - - develop - - master +#branches: +# only: +# - develop +# - master matrix: fast_finish: true environment: matrix: + - APPVEYOR_OCC_VERSION: 7.3.0 - APPVEYOR_OCC_VERSION: 7.4.0 - APPVEYOR_OCC_VERSION: 7.5.0 - APPVEYOR_OCC_VERSION: 7.6.0 + - APPVEYOR_OCC_VERSION: 7.7.0 cache: + - OpenCASCADE-7.3.0-vc14-64.rar - OpenCASCADE-7.4.0-vc14-64.rar - OpenCASCADE-7.5.0-vc14-64.rar - OpenCASCADE-7.6.0-vc14-64.rar + - OpenCASCADE-7.7.0-vc14-64.rar install: - if not exist OpenCASCADE-%APPVEYOR_OCC_VERSION%-vc14-64.rar diff --git a/doc/3rdparty_licenses.md b/doc/3rdparty_licenses.md new file mode 100644 index 00000000..93fb0e89 --- /dev/null +++ b/doc/3rdparty_licenses.md @@ -0,0 +1,1015 @@ +# Copyrights and licenses of third party libraries used by Mayo + +## [Qt](https://www.qt.io) +```` +GNU LESSER GENERAL PUBLIC LICENSE + +The Qt Toolkit is Copyright (C) 2015 The Qt Company Ltd. +Contact: http://www.qt.io/licensing/ + +You may use, distribute and copy the Qt Toolkit under the terms of +GNU Lesser General Public License version 3, which is displayed below. +This license makes reference to the version 3 of the GNU General +Public License, which you can find in the LICENSE.GPLv3 file. + +------------------------------------------------------------------------- + +GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + +Copyright © 2007 Free Software Foundation, Inc. +Everyone is permitted to copy and distribute verbatim copies of this +licensedocument, but changing it is not allowed. + +This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + +0. Additional Definitions. + +As used herein, “this License” refers to version 3 of the GNU Lesser +General Public License, and the “GNU GPL” refers to version 3 of the +GNU General Public License. + +“The Library” refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + +An “Application” is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + +A “Combined Work” is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the “Linked +Version”. + +The “Minimal Corresponding Source” for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + +The “Corresponding Application Code” for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + +1. Exception to Section 3 of the GNU GPL. + +You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + +2. Conveying Modified Versions. + +If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + +a) under this License, provided that you make a good faith effort +to ensure that, in the event an Application does not supply the +function or data, the facility still operates, and performs +whatever part of its purpose remains meaningful, or + +b) under the GNU GPL, with none of the additional permissions of +this License applicable to that copy. + +3. Object Code Incorporating Material from Library Header Files. + +The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + +a) Give prominent notice with each copy of the object code that +the Library is used in it and that the Library and its use are +covered by this License. + +b) Accompany the object code with a copy of the GNU GPL and this +license document. + +4. Combined Works. + +You may convey a Combined Work under terms of your choice that, taken +together, effectively do not restrict modification of the portions of +the Library contained in the Combined Work and reverse engineering for +debugging such modifications, if you also do each of the following: + +a) Give prominent notice with each copy of the Combined Work that +the Library is used in it and that the Library and its use are +covered by this License. + +b) Accompany the Combined Work with a copy of the GNU GPL and this +license document. + +c) For a Combined Work that displays copyright notices during +execution, include the copyright notice for the Library among +these notices, as well as a reference directing the user to the +copies of the GNU GPL and this license document. + +d) Do one of the following: + +0) Convey the Minimal Corresponding Source under the terms of +this License, and the Corresponding Application Code in a form +suitable for, and under terms that permit, the user to +recombine or relink the Application with a modified version of +the Linked Version to produce a modified Combined Work, in the +manner specified by section 6 of the GNU GPL for conveying +Corresponding Source. + +1) Use a suitable shared library mechanism for linking with +the Library. A suitable mechanism is one that (a) uses at run +time a copy of the Library already present on the user's +computer system, and (b) will operate properly with a modified +version of the Library that is interface-compatible with the +Linked Version. + +e) Provide Installation Information, but only if you would +otherwise be required to provide such information under section 6 +of the GNU GPL, and only to the extent that such information is +necessary to install and execute a modified version of the +Combined Work produced by recombining or relinking the Application +with a modified version of the Linked Version. (If you use option +4d0, the Installation Information must accompany the Minimal +Corresponding Source and Corresponding Application Code. If you +use option 4d1, you must provide the Installation Information in +the manner specified by section 6 of the GNU GPL for conveying +Corresponding Source.) + +5. Combined Libraries. + +You may place library facilities that are a work based on the Library +side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + +a) Accompany the combined library with a copy of the same work +based on the Library, uncombined with any other library +facilities, conveyed under the terms of this License. + +b) Give prominent notice with the combined library that part of +it is a work based on the Library, and explaining where to find +the accompanying uncombined form of the same work. + +6. Revised Versions of the GNU Lesser General Public License. + +The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +as you received it specifies that a certain numbered version of the +GNU Lesser General Public License “or any later version” applies to +it, you have the option of following the terms and conditions either +of that published version or of any later version published by the +Free Software Foundation. If the Library as you received it does not +specify a version number of the GNU Lesser General Public License, +you may choose any version of the GNU Lesser General Public License +ever published by the Free Software Foundation. + +If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the Library. +```` + +## [Open Cascade](https://github.com/Open-Cascade-SAS/OCCT) +```` +GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts +as the successor of the GNU Library Public License, version 2, hence +the version number 2.1.] + + Preamble + +The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + +To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + +Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + +When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + +We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + +For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + +Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + +GNU LESSER GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + +You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + +2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + +a) The modified work must itself be a software library. + +b) You must cause the files modified to carry prominent notices +stating that you changed the files and the date of any change. + +c) You must cause the whole of the work to be licensed at no +charge to all third parties under the terms of this License. + +d) If a facility in the modified Library refers to a function or a +table of data to be supplied by an application program that uses +the facility, other than as an argument passed when the facility +is invoked, then you must make a good faith effort to ensure that, +in the event an application does not supply such function or +table, the facility still operates, and performs whatever part of +its purpose remains meaningful. + +(For example, a function in a library to compute square roots has +a purpose that is entirely well-defined independent of the +application. Therefore, Subsection 2d requires that any +application-supplied function or table used by this function must +be optional: if the application does not supply it, the square +root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + +Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + +a) Accompany the work with the complete corresponding +machine-readable source code for the Library including whatever +changes were used in the work (which must be distributed under +Sections 1 and 2 above); and, if the work is an executable linked +with the Library, with the complete machine-readable "work that +uses the Library", as object code and/or source code, so that the +user can modify the Library and then relink to produce a modified +executable containing the modified Library. (It is understood +that the user who changes the contents of definitions files in the +Library will not necessarily be able to recompile the application +to use the modified definitions.) + +b) Use a suitable shared library mechanism for linking with the +Library. A suitable mechanism is one that (1) uses at run time a +copy of the library already present on the user's computer system, +rather than copying library functions into the executable, and (2) +will operate properly with a modified version of the library, if +the user installs one, as long as the modified version is +interface-compatible with the version that the work was made with. + +c) Accompany the work with a written offer, valid for at +least three years, to give the same user the materials +specified in Subsection 6a, above, for a charge no more +than the cost of performing this distribution. + +d) If distribution of the work is made by offering access to copy +from a designated place, offer equivalent access to copy the above +specified materials from the same place. + +e) Verify that the user has already received a copy of these +materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + +It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + +7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + +a) Accompany the combined library with a copy of the same work +based on the Library, uncombined with any other library +facilities. This must be distributed under the terms of the +Sections above. + +b) Give prominent notice with the combined library of the fact +that part of it is a work based on the Library, and explaining +where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + +11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + +Copyright (C) + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in the +library `Frob' (a library for tweaking knobs) written by James Random Hacker. + +, 1 April 1990 +Ty Coon, President of Vice + +That's all there is to it! + + +OCCT_LGPL_EXCEPTION +Open CASCADE exception (version 1.0) to GNU LGPL version 2.1. + +The object code (i.e. not a source) form of a "work that uses the Library" +can incorporate material from a header file that is part of the Library. +As a special exception to the GNU Lesser General Public License version 2.1, +you may distribute such object code incorporating material from header files +provided with the Open CASCADE Technology libraries (including code of CDL +generic classes) under terms of your choice, provided that you give +prominent notice in supporting documentation to this code that it makes use +of or is based on facilities provided by the Open CASCADE Technology software. +```` + +## [fast_float](https://github.com/fastfloat/fast_float) +```` +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, +and distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by +the copyright owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all +other entities that control, are controlled by, or are under common +control with that entity. For the purposes of this definition, +"control" means (i) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or +otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity +exercising permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, +including but not limited to software source code, documentation +source, and configuration files. + +"Object" form shall mean any form resulting from mechanical +transformation or translation of a Source form, including but +not limited to compiled object code, generated documentation, +and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or +Object form, made available under the License, as indicated by a +copyright notice that is included in or attached to the work +(an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object +form, that is based on (or derived from) the Work and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. For the purposes +of this License, Derivative Works shall not include works that remain +separable from, or merely link (or bind by name) to the interfaces of, +the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including +the original version of the Work and any modifications or additions +to that Work or Derivative Works thereof, that is intentionally +submitted to Licensor for inclusion in the Work by the copyright owner +or by an individual or Legal Entity authorized to submit on behalf of +the copyright owner. For the purposes of this definition, "submitted" +means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, +and issue tracking systems that are managed by, or on behalf of, the +Licensor for the purpose of discussing and improving the Work, but +excluding communication that is conspicuously marked or otherwise +designated in writing by the copyright owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity +on behalf of whom a Contribution has been received by Licensor and +subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the +Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of +this License, each Contributor hereby grants to You a perpetual, +worldwide, non-exclusive, no-charge, royalty-free, irrevocable +(except as stated in this section) patent license to make, have made, +use, offer to sell, sell, import, and otherwise transfer the Work, +where such license applies only to those patent claims licensable +by such Contributor that are necessarily infringed by their +Contribution(s) alone or by combination of their Contribution(s) +with the Work to which such Contribution(s) was submitted. If You +institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work +or a Contribution incorporated within the Work constitutes direct +or contributory patent infringement, then any patent licenses +granted to You under this License for that Work shall terminate +as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the +Work or Derivative Works thereof in any medium, with or without +modifications, and in Source or Object form, provided that You +meet the following conditions: + +(a) You must give any other recipients of the Work or +Derivative Works a copy of this License; and + +(b) You must cause any modified files to carry prominent notices +stating that You changed the files; and + +(c) You must retain, in the Source form of any Derivative Works +that You distribute, all copyright, patent, trademark, and +attribution notices from the Source form of the Work, +excluding those notices that do not pertain to any part of +the Derivative Works; and + +(d) If the Work includes a "NOTICE" text file as part of its +distribution, then any Derivative Works that You distribute must +include a readable copy of the attribution notices contained +within such NOTICE file, excluding those notices that do not +pertain to any part of the Derivative Works, in at least one +of the following places: within a NOTICE text file distributed +as part of the Derivative Works; within the Source form or +documentation, if provided along with the Derivative Works; or, +within a display generated by the Derivative Works, if and +wherever such third-party notices normally appear. The contents +of the NOTICE file are for informational purposes only and +do not modify the License. You may add Your own attribution +notices within Derivative Works that You distribute, alongside +or as an addendum to the NOTICE text from the Work, provided +that such additional attribution notices cannot be construed +as modifying the License. + +You may add Your own copyright statement to Your modifications and +may provide additional or different license terms and conditions +for use, reproduction, or distribution of Your modifications, or +for any such Derivative Works as a whole, provided Your use, +reproduction, and distribution of the Work otherwise complies with +the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, +any Contribution intentionally submitted for inclusion in the Work +by You to the Licensor shall be under the terms and conditions of +this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify +the terms of any separate license agreement you may have executed +with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade +names, trademarks, service marks, or product names of the Licensor, +except as required for reasonable and customary use in describing the +origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or +agreed to in writing, Licensor provides the Work (and each +Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +implied, including, without limitation, any warranties or conditions +of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A +PARTICULAR PURPOSE. You are solely responsible for determining the +appropriateness of using or redistributing the Work and assume any +risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, +whether in tort (including negligence), contract, or otherwise, +unless required by applicable law (such as deliberate and grossly +negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, +incidental, or consequential damages of any character arising as a +result of this License or out of the use or inability to use the +Work (including but not limited to damages for loss of goodwill, +work stoppage, computer failure or malfunction, or any and all +other commercial damages or losses), even if such Contributor +has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing +the Work or Derivative Works thereof, You may choose to offer, +and charge a fee for, acceptance of support, warranty, indemnity, +or other liability obligations and/or rights consistent with this +License. However, in accepting such obligations, You may act only +on Your own behalf and on Your sole responsibility, not on behalf +of any other Contributor, and only if You agree to indemnify, +defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason +of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +Copyright 2021 The fast_float authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +```` + +## [fmt](https://github.com/fmtlib/fmt) +```` +Copyright (c) 2012 - present, Victor Zverovich + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +--- Optional exception to the license --- + +As an exception, if, as a result of your compiling your source code, portions of this Software are embedded into a machine-executable object form of such source code, you may redistribute such embedded portions in such object form without including the above copyright and permission notices. +```` + +## [GSL](https://github.com/microsoft/GSL) +```` +Copyright (c) 2015 Microsoft Corporation. All rights reserved. + +This code is licensed under the MIT License (MIT). + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +```` + +## [KDBindings](https://github.com/KDAB/KDBindings) +```` +MIT License + +Copyright (c) 2020-2021 Klarälvdalens Datakonsult AB. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +```` + +## [magic_enum](https://github.com/Neargye/magic_enum) +```` +MIT License + +Copyright (c) 2019 - 2022 Daniil Goncharov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +```` + +## [miniply](https://github.com/vilya/miniply) +```` +MIT License + +Copyright (c) 2019 Vilya Harvey + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +```` + +## [gmio](https://github.com/fougue/gmio) +```` +BSD 2-Clause License + +Copyright (c) 2017, Fougue Ltd. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +```` diff --git a/doc/screenshot_2.png b/doc/screenshot_2.png index 6b46fbfd..6290a107 100644 Binary files a/doc/screenshot_2.png and b/doc/screenshot_2.png differ diff --git a/doc/screenshot_3.png b/doc/screenshot_3.png index a5dcbc3c..e3cb9951 100644 Binary files a/doc/screenshot_3.png and b/doc/screenshot_3.png differ diff --git a/doc/screenshot_4.png b/doc/screenshot_4.png index 7d5d56e4..e7ad3fce 100644 Binary files a/doc/screenshot_4.png and b/doc/screenshot_4.png differ diff --git a/doc/screenshot_6.png b/doc/screenshot_6.png new file mode 100644 index 00000000..ae064bbc Binary files /dev/null and b/doc/screenshot_6.png differ diff --git a/doc/src_modules.odg b/doc/src_modules.odg index e90a5ce0..c7fdb55a 100644 Binary files a/doc/src_modules.odg and b/doc/src_modules.odg differ diff --git a/doc/src_modules.png b/doc/src_modules.png index a21dbdcd..1c73d9f6 100644 Binary files a/doc/src_modules.png and b/doc/src_modules.png differ diff --git a/i18n/mayo_en.qm b/i18n/mayo_en.qm index fb879dde..2bd1c216 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 25f56156..43b76ad8 100644 --- a/i18n/mayo_en.ts +++ b/i18n/mayo_en.ts @@ -4,45 +4,39 @@ AppModule - VeryCoarse - Very Coarse + Very Coarse - Coarse - Coarse + Coarse - Normal - Normal + Normal - Precise - Precise + Precise - VeryPrecise - Very Precise + Very Precise - UserDefined - User Defined + User Defined Mayo::AppModule - + en English - + fr French @@ -163,28 +157,34 @@ Export + VeryCoarse - Very Coarse + Very Coarse + Coarse - Coarse + Coarse + Normal - Normal + Normal + Precise - Precise + Precise + VeryPrecise - Very Precise + Very Precise + UserDefined - User Defined + User Defined @@ -230,66 +230,82 @@ Mesh Defaults - + import Import - + export Export - + Language used for the application. Change will take effect after application restart - + - + In case where multiple documents are opened, make sure the document displayed in the 3D view corresponds to what is selected in the model tree - + - + + Force usage of the fallback Qt widget to display OpenGL graphics. + +When `OFF` the application will try to use OpenGL framebuffer for rendering, this allows to display overlay widgets(eg measure tools panel) with translucid background. However using OpenGL framebuffer might cause troubles for some users(eg empty 3D window) especially on macOS. + +When `ON` the application will use a regular Qt widget for rendering which proved to be more supported. + +This option is applicable when OpenCascade ≥ 7.6 version. Change will take effect after application restart + + + + Controls precision of the mesh to be computed from the BRep shape - + - - For the tesselation of faces the chordal deflection limits the distance between a curve and its tessellation - + + For the tessellation of faces the chordal deflection limits the distance between a curve and its tessellation + - - For the tesselation of faces the angular deflection limits the angle between subsequent segments in a polyline - + + For the tessellation of faces the angular deflection limits the angle between subsequent segments in a polyline + - + Relative computation of edge tolerance If activated, deflection used for the polygonalisation of each edge will be `ChordalDeflection` &#215; `SizeOfEdge`. The deflection used for the faces will be the maximum deflection of their edges. - + - + 3D view manipulation shortcuts configuration to mimic other common CAD applications - + - + + Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference) + + + + Show or hide by default the trihedron centered at world origin. This doesn't affect 3D view of currently opened documents - + - + Enable capping of currently clipped graphics - + - + Enable capping hatch texture of currently clipped graphics - + @@ -322,72 +338,82 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Link With Document Selector - + + forceOpenGlFallbackWidget + Force OpenGL Fallback Widget + + + meshingQuality Quality - + meshingChordalDeflection Chordal Deflection - + meshingAngularDeflection Angular Deflection - + meshingRelative Relative - + navigationStyle View Navigation Style - + defaultShowOriginTrihedron Show Origin Trihedron By Default - + instantZoomFactor Instant Zoom Factor + turnViewAngleIncrement + Turn View Angle Increment + + + cappingOn Capping - + cappingHatchOn Capping Hatch - + color Color - + edgeColor Edge Color - + material Material - + showEgesOn Show Edges - + showNodesOn Show Nodes @@ -395,281 +421,508 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::Application - + Binary Mayo Document Format - + XML Mayo Document Format - - Mayo::BRepMeasureError - - - Entity must be a vertex - - - - - Entity must be a circular edge - - - - - Entity must be a shape(BREP) - - - - - Computation of minimum distance failed - - - - - All entities must be edges - - - - - Entity must be a linear edge - - - - - All entities must be faces - - - - - Entities must not be parallel - - - - - Unknown error - - - Mayo::CliExport Mesh BRep shapes - + Imported - + Exported {} - + Importing... - + Exporting {}... - + - Mayo::DialogAbout + Mayo::Command - - About - + + Orthographic + - - Mayo By Fougue Ltd. - + + Perspective + - - Version %1 (%2bit) - + + Projection + - - Built on %1 at %2 - + + Mode + - - Qt %1 - + + Show Origin Trihedron + - - OpenCascade %1 - + + Show/Hide Origin Trihedron + - - gmio %1 - + + Show Performance Stats + - - %1 By %2 - + + Show/Hide rendering performance statistics + - - - Mayo::DialogInspectXde - - XDE - + + Zoom In + - - ShapeType=%1, Evolution=%2 - + + Zoom Out + - - Yes + + Turn Counter Clockwise - - No + + Turn Clockwise - - Shape - Shape + + %1 files(%2) + %1 is the format identifier and %2 is the file filters string + - - Color - Color + + All files(*.*) + - - Material - + + Select Part File + - - VisMaterial - + + + Mesh BRep shapes + - - Dimension - + + + Import time: {}ms + - - Datum - + + New + - - GeomTolerance - + + New Document + - - Error - + + Anonymous%1 + - - This document is not suitable for XDE - + + Open + - - Attributes - + + Open Documents + - - - Mayo::DialogOptions - - Options - + + Recent files + - - Restore default values - + + %1 | %2 + - - %1 / %2 - + + Clear menu + - - Exchange - + + + Import + Import - - Load from file... - + + Import in current document + - - Save as... - + + + Export selected items + - - - Choose INI file + + No item selected for export - - - INI files(*.ini) - + + Select Output File + - - - + + Export time: {}ms + + + + + + Close "%1" + + + + + Close + + + + + Close all + + + + + Close all documents + + + + + + Close all except current + + + + + Close all except current document + + + + + Close all except "%1" + + + + + Quit + + + + + Report Bug + + + + + About %1 + + + + + + Save View to Image + + + + + + Inspect XDE + + + + + + Options + + + + + Fullscreen + + + + + Switch Fullscreen/Normal + + + + + Show Left Sidebar + + + + + Show/Hide Left Sidebar + + + + + + Previous Document + + + + + + Next Document + + + + + Mayo::DialogAbout + + + About + + + + + Mayo By Fougue Ltd. + + + + + Version %1 (%2bit) + + + + + Built on %1 at %2 + + + + + Qt %1 + + + + + OpenCascade %1 + + + + + gmio %1 + + + + + %1 By %2 + + + + + Mayo::DialogInspectXde + + + XDE + + + + + ShapeType=%1, Evolution=%2 + + + + + Yes + + + + + No + + + + + File Size: %1<br>Dimensions: %2x%3 Depth: %4 + + + + + Error when loading texture file(invalid path?) + + + + + Shape + Shape + + + + Color + Color + + + + Material + + + + + VisMaterial + + + + + Dimension + + + + + Datum + + + + + GeomTolerance + + + + Error + + This document is not suitable for XDE + + + + + Attributes + + + + + Mayo::DialogOptions + + + Options + + + + + Restore default values + + + + + %1 / %2 + + + + + Exchange + + + + + Load from file... + + + + + Save as... + + + + + + Choose INI file + + + + + + INI files(*.ini) + + + + + + Error + + + + '%1' doesn't exist - + '%1' is not readable - + Error when writing to'%1' - + Restore values for default section only - + Restore values for the whole group @@ -803,37 +1056,37 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::GraphicsMeshObjectDriver - + Mesh_Wireframe [Mesh] Wireframe - + Mesh_Shaded [Mesh] Shaded - + Mesh_Shrink [Mesh] Shrink - + color Color - + edgeColor Edge Color - + showEdges Show Edges - + showNodes Show Nodes @@ -891,22 +1144,22 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::GraphicsShapeObjectDriver - + Shape_Wireframe [Shape] Wireframe - + Shape_HiddenLineRemoval [Shape] Hidden Line Removal - + Shape_Shaded [Shape] Shaded - + Shape_ShadedWithFaceBoundary [Shape] Shaded With Edges @@ -914,42 +1167,42 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::IO::DxfReader::Properties - + Scale entities according some factor - + Import text/dimension objects - - Group all objects within a layer into a single coumpound shape + + Group all objects within a layer into a single compound shape - + Name of the font to be used when creating shape for text objects - + scaling Scaling - + importAnnotations Import annotations - + groupLayers Group objects by layer - + fontNameForTextObjects Font for TEXT objects @@ -957,69 +1210,69 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Mayo::IO::GmioAmfWriter::Properties - - Format used when writting `double` values as strings - - - - + Decimal floating point(ex: 392.65) - + Scientific notation(ex: 3.9265E+2) - + Use the shortest representation: decimal or scientific - - Maximum number of significant digits when writting `double` values + + Format used when writing `double` values as strings + + + + + Maximum number of significant digits when writing `double` values - + Write AMF document in ZIP archive containing one file entry - + Filename of the single AMF entry within the ZIP archive. Only applicable if option `{}` is on - + Use the ZIP64 format extensions. Only applicable if option `{}` is on - + float64Format 64bit Float Format - + float64Precision 64bit Float Precision - + createZipArchive Create ZIP Archive - + zipEntryFilename ZIP Entry Filename - + useZip64 Use ZIP64 extensions @@ -1129,9 +1382,68 @@ Only applicable if option `{}` is on - Mayo::IO::OccGltfReader::Properties + Mayo::IO::OccCommon - + + + Undefined + + + + + posYfwd_posZup + + + + + negZfwd_posYup + + + + + Micrometer + + + + + Millimeter + + + + + Centimeter + + + + + Meter + + + + + Kilometer + + + + + Inch + + + + + Foot + + + + + Mile + + + + + Mayo::IO::OccGltfReader::Properties + + skipEmptyNodes Skip Empty Nodes @@ -1154,67 +1466,71 @@ Only applicable if option `{}` is on Mayo::IO::OccGltfWriter::Properties - coordinatesConverter - Coordinates Converter + Coordinates Converter - + transformationFormat Transformation Format - + format Format - + forceExportUV Force UV Export - Coordinate system transformation from OpenCascade to glTF + Source coordinate system transformation - Preferred transformation format for writing into glTF file + Target coordinate system transformation + Preferred transformation format for writing into glTF file + + + + Export UV coordinates even if there is no mapped texture - + Automatically choose most compact representation between Mat4 and TRS - + 4x4 transformation matrix - + Transformation decomposed into Translation vector, Rotation quaternion and Scale factor(T * R * S) - + Name format for exporting nodes - + Name format for exporting meshes - + Write image textures into target file. If set to `false` then texture images will be written as separate files. @@ -1223,14 +1539,14 @@ Applicable only if option `{0}` is set to `{1}` - + Merge faces within a single part. May reduce JSON size thanks to smaller number of primitive arrays - + Prefer keeping 16-bit indexes while merging face. May reduce binary data size thanks to smaller triangle indexes. @@ -1239,32 +1555,42 @@ Applicable only if option `{}` is on - + + inputCoordinateSystem + + + + + outputCoordinateSystem + + + + nodeNameFormat Node Name Format - + meshNameFormat Mesh Name Format - + embedTextures Embed Textures - + mergeFaces Merge Faces - + keepIndices16b Keep 16bit Indices - + Option supported from OpenCascade ≥ v7.6 [option={}, actual version={}] @@ -1401,128 +1727,142 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Mayo::IO::OccObjWriter::Properties + + coordinatesConverter + Coordinates Converter + + + + Source coordinate system transformation + + - Coordinate system transformation from OpenCascade to OBJ + Target coordinate system transformation - - coordinatesConverter - Coordinates Converter + + inputCoordinateSystem + + + + + outputCoordinateSystem + Mayo::IO::OccStepReader::Properties - + productContext Product Context - + assemblyLevel Assembly Level - + preferredShapeRepresentation Preferred Shape Representation - + readShapeAspect Read Shape Aspect - + readSubShapesNames Read Names of sub Shapes - + encoding Encoding - + When reading AP 209 STEP files, allows selecting either only `design` or `analysis`, or both types of products for translation Note that in AP 203 and AP214 files all products should be marked as `design`, so if this mode is set to `analysis`, nothing will be read - + Specifies which data should be read for the products found in the STEP file - + Specifies preferred type of representation of the shape of the product, in case if a STEP file contains more than one representation (i.e. multiple `PRODUCT_DEFINITION_SHAPE` entities) for a single product - + Defines whether shapes associated with the `PRODUCT_DEFINITION_SHAPE` entity of the product via `SHAPE_ASPECT` should be translated. This kind of association was used for the representation of hybrid models (i.e. models whose shape is composed of different types of representations) in AP 203 files before 1998, but it is also used to associate auxiliary information with the sub-shapes of the part. Though STEP translator tries to recognize such cases correctly, this parameter may be useful to avoid unconditionally translation of shapes associated via `SHAPE_ASPECT` entities. - + Indicates whether to read sub-shape names from 'Name' attributes of STEP Representation Items - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `design` - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `analysis` - + Translates all products - + Translate the assembly structure and shapes associated with parts only(not with sub-assemblies) - + Translate only the assembly structure without shapes(a structure of empty compounds). This mode can be useful as an intermediate step in applications requiring specialized processing of assembly parts - + Translate only shapes associated with the product, ignoring the assembly structure (if any). This can be useful to translate only a shape associated with specific product, as a complement to assembly mode - + Translate both the assembly structure and all associated shapes. If both shape and sub-assemblies are associated with the same product, all of them are read and put in a single compound - + Translate all representations(if more than one, put in compound) - + Shift Japanese Industrial Standards - + EUC(Extended Unix Code), multi-byte encoding primarily for Japanese, Korean, and simplified Chinese - + GB(Guobiao) encoding for Simplified Chinese @@ -1530,108 +1870,108 @@ This kind of association was used for the representation of hybrid models (i.e. Mayo::IO::OccStepWriter::Properties - + schema Schema - + lengthUnit Length Unit - + assemblyMode Assembly Mode - + freeVertexMode Mode for Free Vertices - + writeParametericCurves Write Parametric Curves - + writeSubShapesNames Write Names of sub Shapes - + headerAuthor Author(header) - + headerOrganization Organization(header) - + headerOriginatingSystem Originating system(header) - + headerDescription Description(header) - + Version of schema used for the output STEP file - + Defines a unit in which the STEP file should be written. If set to unit other than millimeter, the model is converted to these units during the translation - + Parameter to write all free vertices in one SDR (name and style of vertex are lost) or each vertex in its own SDR (name and style of vertex are exported) - + All free vertices are united into one compound and exported in one shape definition representation (vertex name and style are lost) - + Each vertex is exported in its own `SHAPE DEFINITION REPRESENTATION`(vertex name and style are not lost, but the STEP file size increases) - + Indicates whether parametric curves (curves in parametric space of surface) should be written into the STEP file. It can be disabled in order to minimize the size of the resulting file. - + Indicates whether to write sub-shape names to 'Name' attributes of STEP Representation Items - + Author attribute in STEP header - + Organization(of author) attribute in STEP header - + Originating system attribute in STEP header - + Description attribute in STEP header @@ -1639,23 +1979,38 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::OccStlWriter::Properties - targetFormat - Target Format + Target Format + Ascii - Text + Text + Binary - Binary + Binary + + + + Mayo::IO::OccStlWriterI18N + + + targetFormat + Target Format + + + + + Not all BRep faces are meshed + Mayo::IO::OccVrmlWriter::Properties - + shapeRepresentation Shape Representation @@ -1663,663 +2018,366 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::PlyWriterI18N - + Line that will appear in header - + targetFormat Target Format - + writeColors Write Colors - + defaultColor Default Color - + comment Comment - + Failed to open file - + Unknown host endianness - - - - - Mayo::IO::System - - - Reading file - - - - - Unknown format - - - - - Error during import of '{}' -{} - - - - - No supporting reader - - - - - File read problem - - - - - Transferring file - - - - - - File transfer problem - - - - - Error during export to '{}' -{} - - - - - No supporting writer - - - - - Transfer - - - - - Write - - - - - File write problem - - - - - Mayo::Main - - - Theme for the UI(classic|dark) - - - - - name - - - - - Writes log messages into output file - - - - - Don't filter out debug log messages in release build - - - - - Disable progress reporting in console output(CLI-mode only) - - - - - files - - - - - Files to open at startup, optionally - - - - - [files...] - - - - - Execute unit tests and exit application - - - - - OpenCascade settings file doesn't exist or is not readable [path=%1] - - - - - OpenCascade settings file could not be loaded with QSettings [path=%1] - - - - - Failed to load translation file [path=%1] - - - - - Settings file(INI format) to load at startup - - - - - Mayo the opensource 3D CAD viewer and converter - - - - - - - filepath - File Path - - - - Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) - - - - - Failed to load application settings file [path=%1] - - - - - No input files -> nothing to export - - - - - Failed to load theme '%1' - - - - - Mayo::MainWindow - - - Mayo - - - - - Model tree - - - - - Opened documents - - - - - File system - - - - - Close Left Side Bar - - - - - X= - - - - - - - ? - - - - - Y= - - - - - Z= - - - - - &File - - - - - &Help - - - - - &Tools - - - - - &Window - - - - - &Display - - - - - Projection - - - - - New - - - - - Ctrl+N - - - - - - Import - Import - - - - Quit - - - - - Open - - - - - Ctrl+O - - - - - About Mayo - - - - - Report Bug - - - - - - Options - - - - - Save View to Image - - - - - Export selected items - - - - - Inspect XDE - - - - - - Previous Document - - - - - Alt+Left - - - - - - Next Document - - - - - Alt+Right - + + + + Mayo::IO::System - - Close "%1" + + Reading file - - Ctrl+W + + Unknown format - - Fullscreen + + Error during import of '{}' +{} - - Switch Fullscreen/Normal + + No supporting reader - - F11 + + File read problem - - Show Left Sidebar + + Transferring file - - Show/Hide Left Sidebar + + + File transfer problem - - Alt+0 + + Error during export to '{}' +{} - - Recent files + + No supporting writer - - Show Origin Trihedron + + Transfer - - Show/Hide Origin Trihedron + + Write - - Zoom In + + File write problem + + + Mayo::Main - - Ctrl++ + + Theme for the UI(classic|dark) - - Zoom Out + + name - - Ctrl+- + + Writes log messages into output file - - Close all + + Don't filter out debug log messages in release build - - Ctrl+Shift+W + + Disable progress reporting in console output(CLI-mode only) - - Close all except "%1" + + files - - Perspective + + Files to open at startup, optionally - - Orthographic + + [files...] - - Mode + + Execute unit tests and exit application - - Show Performance Stats + + OpenCascade settings file doesn't exist or is not readable [path=%1] - - Show/Hide rendering performance statistics + + OpenCascade settings file could not be loaded with QSettings [path=%1] - - %1 files(%2) - %1 is the format identifier and %2 is the file filters string + + Failed to load translation file [path=%1] - - All files(*.*) + + Settings file(INI format) to load at startup - - Select Part File + + Mayo the opensource 3D CAD viewer and converter - - Warning - + + + + filepath + File Path - - - Error + + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) - - About %1 + + Failed to load application settings file [path=%1] - - Anonymous%1 + + No input files -> nothing to export - - - Mesh BRep shapes + + Failed to load theme '%1' + + + Mayo::MainWindow - - - Import time: {}ms + + Mayo - - Export time: {}ms + + Model tree - - Select Output File + + Opened documents - - - Data + + File system - - Graphics + + Close Left Side Bar - - Close %1 + + X= - - Close + + + + ? - - Close all except %1 + + Y= - - Close all except current + + Z= - - %1 | %2 + + &File - - Clear menu + + &Help - - - Mayo::MeasureDisplayI18N - - Sum + + &Tools - - (<font color="#FF5500">X</font>{0} <font color="#55FF00">Y</font>{1} <font color="#0077FF">Z</font>{2}){3} + + &Window - - X{0} Y{1} Z{2} + + &Display - - Diameter: {0}{1} - + Import + Import - - Ø{0} + + Options - - Min Distance: {0}{1}<br>Point1: {2}<br>Point2: {3} + + Warning - - Angle: {0}{1} + + + Error - - - {0}: {1}{2} + + + Data - - Length + + Graphics + + + Mayo::MeasureDisplayI18N - Area - Area + Area Mayo::Mesh_DocumentTreeNodeProperties - + NodeCount Count Of Nodes - + TriangleCount Count Of Triangles - + Area Area - + Volume Volume + + Mayo::PointCloud_DocumentTreeNodeProperties + + + PointCount + Point Count + + + + HasColors + Has Colors + + + + CornerMin + Corner Min + + + + CornerMax + Corner Max + + Mayo::PropertyEditorI18N @@ -2331,33 +2389,33 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::PropertyItemDelegate - + %1d - + %1h - + %1min - + %1s - - + + %1%2 - + ERROR no stringifier for property type '%1' @@ -2494,7 +2552,7 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::WidgetFileSystem - + %1 Size: %2 Last modified: %3 @@ -2504,62 +2562,62 @@ Last modified: %3 Mayo::WidgetGuiDocument - + Fit All - + Edit clip planes - + Explode assemblies - + Measure shapes - + Isometric - + Back - + Front - + Left - + Right - + Top - + Bottom - + <b>Left-click</b>: popup menu of pre-defined views <b>CTRL+Left-click</b>: apply '%1' view @@ -2758,7 +2816,7 @@ Read: %5 - + Select entities to measure @@ -2784,15 +2842,30 @@ Read: %5 Mayo::WidgetModelTreeBuilder_Xde - + instanceNameFormat Name Format Of Assembly Instances - + Show {} + + + Instance + + + + + Product + + + + + Both + + Mayo::WidgetPropertiesEditor @@ -2815,82 +2888,82 @@ Read: %5 Mayo::XCaf_DocumentTreeNodeProperties - + Name Name - + Shape Shape - + XdeShape XDE Shape Type - + XdeLayer Layer - + Color Color - + Location Location - + Centroid Centroid - + Area Area - + Volume Volume - + MaterialDensity Material Density - + MaterialName Material Name - + ProductName Product Name - + ProductColor Product Color - + ProductCentroid Product Centroid - + ProductArea Product Area - + ProductVolume Product Volume @@ -2919,76 +2992,15 @@ Read: %5 Sub - - OccCommon - - - - Undefined - - - - - posYfwd_posZup - - - - - negZfwd_posYup - - - - - Micrometer - - - - - Millimeter - - - - - Centimeter - - - - - Meter - - - - - Kilometer - - - - - Inch - - - - - Foot - - - - - Mile - - - OccStlWriter::Properties - Ascii - Text + Text - Binary - Binary + Binary @@ -3162,22 +3174,4 @@ Read: %5 - - WidgetModelTreeBuilder_Xde - - - Instance - - - - - Product - - - - - Both - - - diff --git a/i18n/mayo_fr.qm b/i18n/mayo_fr.qm index aa56f32f..d0002376 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 1702cfe3..502e1072 100644 --- a/i18n/mayo_fr.ts +++ b/i18n/mayo_fr.ts @@ -4,45 +4,39 @@ AppModule - VeryCoarse - Très grossière + Très grossière - Coarse - Grossière + Grossière - Normal - Normale + Normale - Precise - Précise + Précise - VeryPrecise - Très précise + Très précise - UserDefined - Custom + Custom Mayo::AppModule - + en Anglais - + fr Français @@ -203,28 +197,34 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Export + VeryCoarse - Très grossière + Très grossière + Coarse - Grossière + Grossière + Normal - Normale + Normale + Precise - Précise + Précise + VeryPrecise - Très précise + Très précise + UserDefined - Custom + Custom @@ -270,42 +270,67 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Maillage par défauts - + import Import - + export Export - + Language used for the application. Change will take effect after application restart Langage de l'application. Tout changement sera effectif après redémarrage de l'application - + In case where multiple documents are opened, make sure the document displayed in the 3D view corresponds to what is selected in the model tree Dans le cas où plusieurs documents sont ouverts, fait en sort que le document affiché dans la vue 2D correspond à ce qui est sélectionné dans l'arborescence du modèle - + + Force usage of the fallback Qt widget to display OpenGL graphics. + +When `OFF` the application will try to use OpenGL framebuffer for rendering, this allows to display overlay widgets(eg measure tools panel) with translucid background. However using OpenGL framebuffer might cause troubles for some users(eg empty 3D window) especially on macOS. + +When `ON` the application will use a regular Qt widget for rendering which proved to be more supported. + +This option is applicable when OpenCascade ≥ 7.6 version. Change will take effect after application restart + Force l'utilisation du widget Qt de secours pour le rendu des graphismes OpenGL. + +Quand l'option est désactivée alors l'application essaiera d'employer un framebuffer OpenGL pour le rendu, permettant la translucidité du fond des widgets Qt (voir le panneau des outils de mesure). Mais la technique du framebuffer OpenGL peut causer des problèmes pour certains utilisateurs (par ex : fenêtre 3D vide) en particulier sur macOS. + +Quand l'option est activée alors l'application utilisera un widget Qt normal pour le rendu OpenGL, solution ayant fait ses preuves et mieux supportée. + +Cette option est appliquable seulement avec la version ≥ 7.6 d'OpenCascade. Tout changement sera effectif après redémarrage de l'application + + + Controls precision of the mesh to be computed from the BRep shape Contrôle la précision du maillage calculé à partir de la forme BRep - - For the tesselation of faces the chordal deflection limits the distance between a curve and its tessellation + + For the tessellation of faces the chordal deflection limits the distance between a curve and its tessellation Pour la tesselation des faces, la déflection chordale limite la distance entre une courbe et sa discrétisation - - For the tesselation of faces the angular deflection limits the angle between subsequent segments in a polyline + + For the tessellation of faces the angular deflection limits the angle between subsequent segments in a polyline Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne - + For the tesselation of faces the chordal deflection limits the distance between a curve and its tessellation + Pour la tesselation des faces, la déflection chordale limite la distance entre une courbe et sa discrétisation + + + For the tesselation of faces the angular deflection limits the angle between subsequent segments in a polyline + Pour la tesselation des faces, la déflection angulaire limite l'angle entre les segments successifs d'une polyligne + + + Relative computation of edge tolerance If activated, deflection used for the polygonalisation of each edge will be `ChordalDeflection` &#215; `SizeOfEdge`. The deflection used for the faces will be the maximum deflection of their edges. @@ -314,22 +339,27 @@ If activated, deflection used for the polygonalisation of each edge will be `Cho Si actif, la déflection utilisée pour la polygonisation de chaque arête sera de `DéflectionChordale` &#215; `TailleArête`. La déflection utilisée pour les faces sera la déflection maximale de ses arêtes. - + 3D view manipulation shortcuts configuration to mimic other common CAD applications Configuration des raccourcis pour manipuler la vue 3D, permet d'imiter les autres application CAO - + + Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference) + Incrément angulaire utilisé pour tourner la vue 3D autour de la normale au plan de vue (axe Z de référence) + + + Show or hide by default the trihedron centered at world origin. This doesn't affect 3D view of currently opened documents Montrer/cacher par défaut le trièdre positionné à l'orgine "monde". N'affecte pas la vue 3D des documents actuellement ouverts - + Enable capping of currently clipped graphics Activer le bouchage des graphismes actuellement coupés - + Enable capping hatch texture of currently clipped graphics Activer le hachage texturé pour le bouchage des graphismes actuellement coupés @@ -364,72 +394,82 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Lier au sélecteur de documents - + + forceOpenGlFallbackWidget + Forcer usage du widget OpenGL de secours + + + meshingQuality Qualité - + meshingChordalDeflection Déflection chordale - + meshingAngularDeflection Déflection angulaire - + meshingRelative Relatif - + navigationStyle Style de navigation de la vue - + defaultShowOriginTrihedron Afficher le trihèdre Origine par défaut - + instantZoomFactor Coefficient du zoom instantané + turnViewAngleIncrement + Incrément de rotation de la vue + + + cappingOn Bouchage - + cappingHatchOn Bouchages avec hachures - + color Couleur - + edgeColor Couleur des arêtes - + material Matériau - + showEgesOn Afficher les arêtes - + showNodesOn Afficher les nœuds @@ -437,12 +477,12 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::Application - + Binary Mayo Document Format - + XML Mayo Document Format @@ -454,49 +494,40 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::BRepMeasureError - Entity must be a vertex - L'entité doit être un sommet + L'entité doit être un sommet - Entity must be a circular edge - L'entité doit être une arête circulaire + L'entité doit être une arête circulaire - Entity must be a shape(BREP) - L'entité doit une forme BREP + L'entité doit une forme BREP - Computation of minimum distance failed - Échec du calcul de la distance minimum + Échec du calcul de la distance minimum - All entities must be edges - Toutes les entités doivent être des arêtes + Toutes les entités doivent être des arêtes - Entity must be a linear edge - L'entité doit une arête linéaire + L'entité doit une arête linéaire - All entities must be faces - Toutes les entités doivent être des faces + Toutes les entités doivent être des faces - Entities must not be parallel - Les entités ne doivent pas être parallèles + Les entités ne doivent pas être parallèles - Unknown error - Erreur inconnue + Erreur inconnue @@ -527,6 +558,282 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Export de {} en cours ... + + Mayo::Command + + + Orthographic + Orthographique + + + + Perspective + + + + + Projection + + + + + Mode + Mode + + + + Show Origin Trihedron + Montrer le trihèdre Origine + + + + Show/Hide Origin Trihedron + Montrer/cacher le trihèdre Origine + + + + Show Performance Stats + Montrer les statistiques de rendu + + + + Show/Hide rendering performance statistics + Montrer/cacher les statistiques de rendu + + + + Zoom In + Zoom avant + + + + Zoom Out + Zoom arrière + + + + Turn Counter Clockwise + Tourner dans le sens anti-horaire + + + + Turn Clockwise + Tourner dans le sens horaire + + + + %1 files(%2) + %1 is the format identifier and %2 is the file filters string + %1 fichiers (%2) + + + + All files(*.*) + Tous les fichiers (*.*) + + + + Select Part File + Selectionner fichier pièce + + + + + Mesh BRep shapes + Maillage des formes BRep + + + + + Import time: {}ms + Durée import: {}ms + + + + New + Nouveau + + + + New Document + Nouveau Document + + + + Anonymous%1 + Anonyme%1 + + + + Open + Ouvrir + + + + Open Documents + Ouvrir des documents + + + + Recent files + Fichiers récents + + + + %1 | %2 + + + + + Clear menu + Vider le menu + + + + + Import + Importer + + + + Import in current document + Importer dans le document courant + + + + + Export selected items + Exporter les éléments sélectionnées + + + + No item selected for export + Aucun élément sélectionné pour l'export + + + + Select Output File + Sélection fichier de sortie + + + + Export time: {}ms + Durée export: {}ms + + + + + Close "%1" + Fermer "%1" + + + + Close + Fermer + + + + Close all + Tout fermer + + + + Close all documents + Fermer tous les documents + + + + + Close all except current + Tout fermer sauf document courant + + + + Close all except current document + Tout fermer sauf document courant + + + + Close all except "%1" + Tout fermer sauf "%1" + + + + Quit + Quitter + + + + Report Bug + Signaler un bug + + + + About %1 + À propos %1 + + + + + Save View to Image + Sauvegarder la vue vers une image + + + + + Inspect XDE + Inspection XDE + + + + + Options + Options + + + + Fullscreen + Plein-écran + + + + Switch Fullscreen/Normal + Basculer plein-écran/normal + + + + Show Left Sidebar + Montrer le bandeau vertical fixé à gauche + + + + Show/Hide Left Sidebar + Montrer/cacher le bandeau vertical fixé à gauche + + + + + Previous Document + Document précédent + + + + + Next Document + Document suivant + + + + Mayo::CommandCloseCurrentDocument + + Close "%1" + Fermer "%1" + + + Close + Fermer + + Mayo::DialogAbout @@ -578,67 +885,77 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera XDE - + ShapeType=%1, Evolution=%2 - + Yes Oui - + No Non - + + File Size: %1<br>Dimensions: %2x%3 Depth: %4 + + + + + Error when loading texture file(invalid path?) + + + + Shape Forme - + Color Couleur - + Material - + VisMaterial - + Dimension - + Datum - + GeomTolerance - + Error Erreur - + This document is not suitable for XDE Ce document n'est pas XDE-compatible - + Attributes Attributs @@ -656,66 +973,66 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Restaurer les valeurs par défaut - + %1 / %2 %1 / %2 - + Exchange Échanger - + Load from file... Charger le fichier ... - + Save as... Sauvergarder vers ... - - + + Choose INI file Choisir fichier INI - - + + INI files(*.ini) Fichiers INI(*.ini) + - - + Error Erreur - + '%1' doesn't exist '%1' n'existe pas - + '%1' is not readable '%1' ne dispose pas des permissions de lecture - + Error when writing to'%1' Erreur lors de l'écriture vers '%1' - + Restore values for default section only Restaurer les valeurs seulement pour la section par défaut - + Restore values for the whole group Restaure les valeurs pour tout le groupe @@ -849,37 +1166,37 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::GraphicsMeshObjectDriver - + Mesh_Wireframe [Maillage] Filaire - + Mesh_Shaded [Maillage] Ombré - + Mesh_Shrink [Maillage] Rétréci - + color Couleur - + edgeColor Couleur des arêtes - + showEdges Montrer les arêtes - + showNodes Montrer les nœuds @@ -937,22 +1254,22 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::GraphicsShapeObjectDriver - + Shape_Wireframe [Forme] Filaire - + Shape_HiddenLineRemoval [Forme] Suppression des arêtes cachées - + Shape_Shaded [Forme] Ombré - + Shape_ShadedWithFaceBoundary [Forme] Ombré avec arêtes @@ -960,42 +1277,46 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::IO::DxfReader::Properties - + Scale entities according some factor Redimensionner les entités selon un facteur d'échelle - + Import text/dimension objects Importer les entités texte et dimension - Group all objects within a layer into a single coumpound shape + Grouper toutes les entités d'une même couche dans une forme compound BREP + + + + Group all objects within a layer into a single compound shape Grouper toutes les entités d'une même couche dans une forme compound BREP - + Name of the font to be used when creating shape for text objects Nom de la police de caractères à utiliser lors de la création des formes correspondantes aux entités TEXT - + scaling Mise à l'échelle - + importAnnotations Import des annotations - + groupLayers Grouper les entitiés par couche - + fontNameForTextObjects Police de caractères pour le texte @@ -1003,44 +1324,42 @@ Si actif, la déflection utilisée pour la polygonisation de chaque arête sera Mayo::IO::GmioAmfWriter::Properties - Format used when writting `double` values as strings - Format à utiliser lors de l'écriture des valeurs de type `double`en chaînes de caractères + Format à utiliser lors de l'écriture des valeurs de type `double`en chaînes de caractères - + Decimal floating point(ex: 392.65) Décimal à virgule flottante (ex: 392,65) - + Scientific notation(ex: 3.9265E+2) Notation scientifique (ex: 3,9265E+2) - + Use the shortest representation: decimal or scientific Utiliser la notation la plus compacte : décimale ou scientifique - Maximum number of significant digits when writting `double` values - Nombre maximal de chiffres significatifs lors de l'écriture de valeurs de type `double` + Nombre maximal de chiffres significatifs lors de l'écriture de valeurs de type `double` - + Write AMF document in ZIP archive containing one file entry Écrire le document AMF dans une archive ZIP contenant une entrée de fichier - + Filename of the single AMF entry within the ZIP archive. Only applicable if option `{}` is on Nom de l'entrée du fichier AMF dans l'archive ZIP. Seulement applicable si l'option `{}` est activée - + Use the ZIP64 format extensions. Only applicable if option `{}` is on Utiliser les extensions de format ZIP64. @@ -1059,27 +1378,37 @@ Only applicable if option `%1` is on Seulement applicable si l'option `%1` est activée - + + Format used when writing `double` values as strings + Format à utiliser lors de l'écriture des valeurs de type `double`en chaînes de caractères + + + + Maximum number of significant digits when writing `double` values + Nombre maximal de chiffres significatifs lors de l'écriture de valeurs de type `double` + + + float64Format Format nombres flottants 64bit - + float64Precision Précision nombres flottants 64bit - + createZipArchive Créer une archive ZIP - + zipEntryFilename Nom du fichier de l'entrée ZIP - + useZip64 Utiliser les extensions ZIP64 @@ -1203,48 +1532,60 @@ Seulement applicable si l'option `%1` est activée Mayo::IO::OccCommon + + Undefined - Indéfini + Indéfini + posYfwd_posZup - +Zup + +Zup + negZfwd_posYup - +Yup + +Yup + Micrometer - Micromètre + Micromètre + Millimeter - Millimètre + Millimètre + Centimeter - Centimètre + Centimètre + Meter - Mètre + Mètre + Kilometer - Kilomètre + Kilomètre + Inch - Pouce + Pouce + Foot - Pied + Pied + Mile - Mile + Mile @@ -1277,67 +1618,75 @@ Seulement applicable si l'option `%1` est activée Mayo::IO::OccGltfWriter::Properties - coordinatesConverter - Convertisseur de coordonnées + Convertisseur de coordonnées - + transformationFormat Format de transformation - + format Format - + forceExportUV Forcer l'export UV - Coordinate system transformation from OpenCascade to glTF - Transformation des coordonnées d'OpenCascade vers glTF + Transformation des coordonnées d'OpenCascade vers glTF + + + + Source coordinate system transformation + Transformation du système de coordonnées source + Target coordinate system transformation + Transformation du système de coordonnées cible + + + Preferred transformation format for writing into glTF file Transformation préférée pour l'écriture des fichiers glTF - + Export UV coordinates even if there is no mapped texture Exporter les coordonnées UV même si aucune texture mappée - + Automatically choose most compact representation between Mat4 and TRS Choisir automatiquement la représentation la plus compacte entre Mat4 et TRS - + 4x4 transformation matrix Mactrice de transformation 4x4 - + Transformation decomposed into Translation vector, Rotation quaternion and Scale factor(T * R * S) Transformation décomposée en vecteur de translation, quaternion de rotation et facteur d'échelle (T x R x S) - + Name format for exporting nodes Format du nom utilisé pour exporter la hiérarchie de nœuds - + Name format for exporting meshes Format du nom utilisé pour exporter la hiérarchie de maillages - + Write image textures into target file. If set to `false` then texture images will be written as separate files. @@ -1362,7 +1711,7 @@ If set to `false` then texture images will be written as separate files. Applicable only if option `{}` is set to `{}` - + Merge faces within a single part. May reduce JSON size thanks to smaller number of primitive arrays @@ -1371,7 +1720,7 @@ May reduce JSON size thanks to smaller number of primitive arrays Peut réduire la taille JSON grâce à une quantité réduite de tableaux de primitives - + Prefer keeping 16-bit indexes while merging face. May reduce binary data size thanks to smaller triangle indexes. @@ -1384,32 +1733,42 @@ Peut réduite la taille des données grâce à une quantité réduite d'ind Applicable seulement si l'option `{}` est cochée - + + inputCoordinateSystem + Système de coordonnées d'entrée + + + + outputCoordinateSystem + Système de coordonnées de sortie + + + nodeNameFormat Format du nom pour les nœuds - + meshNameFormat Format du nom pour les maillages - + embedTextures Incorporer les textures dans le même fichier cible - + mergeFaces Fusionner les faces - + keepIndices16b Utiliser des indices 16bit - + Option supported from OpenCascade ≥ v7.6 [option={}, actual version={}] Option prise en charge à partir de OpenCascade ≥ v7.6 [option={}, version actuelle={}] @@ -1547,127 +1906,141 @@ The processor also decides to re-compute either the 3D or the 2D curve even if b Mayo::IO::OccObjWriter::Properties - - Coordinate system transformation from OpenCascade to OBJ - + coordinatesConverter + Convertisseur de coordonnées + + + + Source coordinate system transformation + Transformation du système de coordonnées source + + + + Target coordinate system transformation + Transformation du système de coordonnées cible + + + + inputCoordinateSystem + Système de coordonnées d'entrée - - coordinatesConverter - Convertisseur de coordonnées + + outputCoordinateSystem + Système de coordonnées de sortie Mayo::IO::OccStepReader::Properties - + productContext Context du produit - + assemblyLevel Niveau assemblage - + preferredShapeRepresentation Représentation des formes préférée - + readShapeAspect Lire l'aspect des formes - + readSubShapesNames Lire le nom des sous-formes - + encoding Encodage - + When reading AP 209 STEP files, allows selecting either only `design` or `analysis`, or both types of products for translation Note that in AP 203 and AP214 files all products should be marked as `design`, so if this mode is set to `analysis`, nothing will be read - + Specifies which data should be read for the products found in the STEP file - + Specifies preferred type of representation of the shape of the product, in case if a STEP file contains more than one representation (i.e. multiple `PRODUCT_DEFINITION_SHAPE` entities) for a single product - + Defines whether shapes associated with the `PRODUCT_DEFINITION_SHAPE` entity of the product via `SHAPE_ASPECT` should be translated. This kind of association was used for the representation of hybrid models (i.e. models whose shape is composed of different types of representations) in AP 203 files before 1998, but it is also used to associate auxiliary information with the sub-shapes of the part. Though STEP translator tries to recognize such cases correctly, this parameter may be useful to avoid unconditionally translation of shapes associated via `SHAPE_ASPECT` entities. - + Indicates whether to read sub-shape names from 'Name' attributes of STEP Representation Items - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `design` - + Translate only products that have `PRODUCT_DEFINITION_CONTEXT` with field `life_cycle_stage` set to `analysis` - + Translates all products - + Translate the assembly structure and shapes associated with parts only(not with sub-assemblies) - + Translate only the assembly structure without shapes(a structure of empty compounds). This mode can be useful as an intermediate step in applications requiring specialized processing of assembly parts - + Translate only shapes associated with the product, ignoring the assembly structure (if any). This can be useful to translate only a shape associated with specific product, as a complement to assembly mode - + Translate both the assembly structure and all associated shapes. If both shape and sub-assemblies are associated with the same product, all of them are read and put in a single compound - + Translate all representations(if more than one, put in compound) - + Shift Japanese Industrial Standards - + EUC(Extended Unix Code), multi-byte encoding primarily for Japanese, Korean, and simplified Chinese - + GB(Guobiao) encoding for Simplified Chinese @@ -1675,108 +2048,108 @@ This kind of association was used for the representation of hybrid models (i.e. Mayo::IO::OccStepWriter::Properties - + schema Schéma - + lengthUnit Unité de longueur - + assemblyMode Mode de l'assemblage - + freeVertexMode Mode des sommets libres - + writeParametericCurves Écrire les courbes paramétriques - + writeSubShapesNames Écrire le nom des sous-formes - + headerAuthor Auteur (en-tête) - + headerOrganization Organisation (en-tête) - + headerOriginatingSystem Système source (en-tête) - + headerDescription Description (en-tête) - + Version of schema used for the output STEP file Version du schéma à utiliser pour le fichier STEP de sortie - + Defines a unit in which the STEP file should be written. If set to unit other than millimeter, the model is converted to these units during the translation - + Parameter to write all free vertices in one SDR (name and style of vertex are lost) or each vertex in its own SDR (name and style of vertex are exported) - + All free vertices are united into one compound and exported in one shape definition representation (vertex name and style are lost) - + Each vertex is exported in its own `SHAPE DEFINITION REPRESENTATION`(vertex name and style are not lost, but the STEP file size increases) - + Indicates whether parametric curves (curves in parametric space of surface) should be written into the STEP file. It can be disabled in order to minimize the size of the resulting file. - + Indicates whether to write sub-shape names to 'Name' attributes of STEP Representation Items - + Author attribute in STEP header - + Organization(of author) attribute in STEP header - + Originating system attribute in STEP header - + Description attribute in STEP header @@ -1784,23 +2157,38 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::OccStlWriter::Properties - targetFormat - Format cible + Format cible + Ascii - Texte + Texte + Binary - Binaire + Binaire + + + + Mayo::IO::OccStlWriterI18N + + + targetFormat + Format cible + + + + + Not all BRep faces are meshed + Les faces BRep ne sont pas toutes maillées Mayo::IO::OccVrmlWriter::Properties - + shapeRepresentation Représentation des formes @@ -1808,37 +2196,37 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::IO::PlyWriterI18N - + Line that will appear in header Texte qui apparaîtra dans l'en-tête - + targetFormat Format cible - + writeColors Écrire la couleur des sommets - + defaultColor Couleur par défaut - + comment Commentaire - + Failed to open file Impossible d'ouvrir le fichier - + Unknown host endianness Boutisme du CPU inconnu @@ -1935,62 +2323,62 @@ It can be disabled in order to minimize the size of the resulting file. Mayo, une visionneuse 3D en code libre basée surQt5/OpenCascade - + Theme for the UI(classic|dark) Thème de l'IHM (classic|dark) - + name nom - + Writes log messages into output file Écrit les messages de log dans un fichier de sortie - + Don't filter out debug log messages in release build Ne pas filtrer les messages de debug dans la version "release" - + Disable progress reporting in console output(CLI-mode only) Désactiver l'indicateur de progression dans la sortie console (mode CLI seulement) - + files files - + Files to open at startup, optionally Fichiers à ouvrir au démarrage, optionnel - + [files...] [fichiers ...] - + Execute unit tests and exit application Exécuter les tests unitaires et quitter l'application - + OpenCascade settings file doesn't exist or is not readable [path=%1] Le fichier de configuration OpenCascade n'existe pas ou non lisible [chemin=%1] - + OpenCascade settings file could not be loaded with QSettings [path=%1] Le fichier de configuration OpenCascade n'a pu être chargé par QSettings [chemin=%1] - + Failed to load translation file [path=%1] Échec chargement du fichier de traductions [chemin=%1] @@ -2003,7 +2391,7 @@ It can be disabled in order to minimize the size of the resulting file. Export de {} en cours ... - + Failed to load application settings file [path=%1] Échec chargement du fichier de configuration [chemin=%1] @@ -2014,24 +2402,24 @@ It can be disabled in order to minimize the size of the resulting file. - + Settings file(INI format) to load at startup Fichier de configuration (format INI) à charger au démarrage - + Mayo the opensource 3D CAD viewer and converter Mayo le visualiseur et convertisseur 3D pour la CAO - - - + + + filepath - + Export opened files into an output file, can be repeated for different formats(eg. -e file.stp -e file.igs...) Exporter des fichiers dans un fichier de sortie, répétable selon les différents formats supportés (par exemple -e file.stp -e file.igs ...) @@ -2056,12 +2444,12 @@ It can be disabled in order to minimize the size of the resulting file. Export de %1 en cours ... - + No input files -> nothing to export Auncun fichier en entrée -> aucun export - + Failed to load theme '%1' Impossible de charger le thème '%1' @@ -2129,415 +2517,287 @@ It can be disabled in order to minimize the size of the resulting file. &Fichier - + &Help &Aide - + &Tools &Outils - + &Window F&enêtre - + &Display A&ffichage - - Projection - - - - New - Nouveau - - - - Ctrl+N - + Nouveau - - Import - Importer + Importer - Quit - Quitter + Quitter - Open - Ouvrir - - - - Ctrl+O - + Ouvrir - About Mayo - À propos de Mayo + À propos de Mayo - Report Bug - Signaler un bug + Signaler un bug - - + Options - Save View to Image - Sauvegarder la vue vers une image + Sauvegarder la vue vers une image - Export selected items - Exporter les éléments sélectionnées + Exporter les éléments sélectionnées - Inspect XDE - Inspection XDE + Inspection XDE - - Previous Document - Document précédent - - - - Alt+Left - + Document précédent - - Next Document - Document suivant - - - - Alt+Right - + Document suivant - Close "%1" - Fermer "%1" - - - - Ctrl+W - + Fermer "%1" - Fullscreen - Plein-écran + Plein-écran - Switch Fullscreen/Normal - Basculer plein-écran/normal - - - - F11 - - - - - Show Left Sidebar - - - - - Show/Hide Left Sidebar - - - - - Alt+0 - + Basculer plein-écran/normal - Recent files - Fichiers récents + Fichiers récents - Show Origin Trihedron - Montrer le trihèdre Origine + Montrer le trihèdre Origine - Show/Hide Origin Trihedron - Montrer/cacher le trihèdre Origine + Montrer/cacher le trihèdre Origine - Zoom In - Zoom avant - - - - Ctrl++ - + Zoom avant - Zoom Out - Zoom arrière - - - - Ctrl+- - + Zoom arrière - Close all - Tout fermer - - - - Ctrl+Shift+W - + Tout fermer - Close all except "%1" - Tout fermer sauf "%1" - - - - Perspective - + Tout fermer sauf "%1" - Orthographic - Orthographique + Orthographique - Mode - Mode + Mode - Show Performance Stats - Montrer les statistiques de rendu + Montrer les statistiques de rendu - Show/Hide rendering performance statistics - Montrer/cacher les statistiques de rendu + Montrer/cacher les statistiques de rendu - %1 files(%2) %1 is the format identifier and %2 is the file filters string - %1 fichiers (%2) + %1 fichiers (%2) - All files(*.*) - Tous les fichiers (*.*) + Tous les fichiers (*.*) - Select Part File - Selectionner fichier pièce + Selectionner fichier pièce - + Warning Avertissement - - + + Error Erreur - About %1 - À propos %1 + À propos %1 - Anonymous%1 - Anonyme%1 + Anonyme%1 - - Mesh BRep shapes - Maillage des formes BRep + Maillage des formes BRep - - Import time: {}ms - Durée import: {}ms + Durée import: {}ms - Export time: {}ms - Durée export: {}ms + Durée export: {}ms Import time: %1ms Temps import : %1ms - Select Output File - Sélection fichier de sortie + Sélection fichier de sortie Export time: %1ms Temps export : %1ms - - + + Data Données - + Graphics Graphismes - Close %1 - Fermer %1 + Fermer %1 - Close - Fermer + Fermer - Close all except %1 - Tout fermer sauf %1 + Tout fermer sauf %1 - Close all except current - Tout fermer sauf document courant - - - - %1 | %2 - + Tout fermer sauf document courant - Clear menu - Vider le menu + Vider le menu Mayo::MeasureDisplayI18N - Sum - Total - - - - (<font color="#FF5500">X</font>{0} <font color="#55FF00">Y</font>{1} <font color="#0077FF">Z</font>{2}){3} - - - - - X{0} Y{1} Z{2} - + Total - Diameter: {0}{1} - Diamètre: {0}{1} - - - - Ø{0} - + Diamètre: {0}{1} - Min Distance: {0}{1}<br>Point1: {2}<br>Point2: {3} - Distance Min: {0}{1}<br>Point1: {2}<br>Point2: {3} - - - - Angle: {0}{1} - - - - - - {0}: {1}{2} - + Distance Min: {0}{1}<br>Point1: {2}<br>Point2: {3} - Length - Longueur + Longueur - Area - Aire + Aire Mayo::Mesh_DocumentTreeNodeProperties - + NodeCount Nombre de nœuds - + TriangleCount Nombre de triangles - + Area Aire - + Volume Volume + + Mayo::PointCloud_DocumentTreeNodeProperties + + + PointCount + Nombre de points + + + + HasColors + Couleurs + + + + CornerMin + Coin min + + + + CornerMax + Coin max + + Mayo::PropertyEditorI18N @@ -2549,33 +2809,33 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::PropertyItemDelegate - + %1d %1j - + %1h %1h - + %1min %1min - + %1s %1s - - + + %1%2 %1%2 - + ERROR no stringifier for property type '%1' ERREUR aucune transformation en string pour les propriétés de type '%1' @@ -2743,7 +3003,7 @@ It can be disabled in order to minimize the size of the resulting file. Mayo::WidgetFileSystem - + %1 Size: %2 Last modified: %3 @@ -2757,62 +3017,62 @@ Modifié le: %3 {1 Mayo::WidgetGuiDocument - + Fit All Adapter à tout - + Edit clip planes Éditer les plans de coupe - + Explode assemblies Éclater l'assemblage - + Measure shapes Mesures - + Isometric Isométrique - + Back Arrière - + Front Devant - + Left Gauche - + Right Droit - + Top Haut - + Bottom Bas - + <b>Left-click</b>: popup menu of pre-defined views <b>CTRL+Left-click</b>: apply '%1' view <b>Click gauche</b> : menu déroulant des vues pré-définies @@ -3043,7 +3303,7 @@ Lu: %5 Unité angle - + Select entities to measure Sélectionner les entités à mesurer @@ -3069,12 +3329,12 @@ Lu: %5 Mayo::WidgetModelTreeBuilder_Xde - + instanceNameFormat Format des noms d'instance - + Show {} Montrer {} @@ -3083,16 +3343,19 @@ Lu: %5 Montrer %1 + Instance - Instance + Instance + Product - Produit + Produit + Both - Les Deux + Les Deux @@ -3116,82 +3379,82 @@ Lu: %5 Mayo::XCaf_DocumentTreeNodeProperties - + Name Nom - + Shape Forme - + XdeShape Forme XDE - + XdeLayer Couche - + Color Couleur - + Location Placement - + Centroid Centroïde - + Area Aire - + Volume Volume - + MaterialDensity Densité matière - + MaterialName Nom matière - + ProductName Nom du produit - + ProductColor Couleur du produit - + ProductCentroid Centroïde du produit - + ProductArea Aire du produit - + ProductVolume Volume du produit @@ -3223,73 +3486,59 @@ Lu: %5 OccCommon - - Undefined - Indéfini + Indéfini - posYfwd_posZup - +Zup + +Zup - negZfwd_posYup - +Yup + +Yup - Micrometer - Micromètre + Micromètre - Millimeter - Millimètre + Millimètre - Centimeter - Centimètre + Centimètre - Meter - Mètre + Mètre - Kilometer - Kilomètre + Kilomètre - Inch - Pouce + Pouce - Foot - Pied + Pied - Mile - Mile + Mile OccStlWriter::Properties - Ascii - Texte + Texte - Binary - Binaire + Binaire @@ -3466,19 +3715,16 @@ Lu: %5 WidgetModelTreeBuilder_Xde - Instance - Instance + Instance - Product - Produit + Produit - Both - Les Deux + Les Deux diff --git a/images/themes/classic/turn-ccw.svg b/images/themes/classic/turn-ccw.svg new file mode 100644 index 00000000..1d43ffcc --- /dev/null +++ b/images/themes/classic/turn-ccw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/classic/turn-cw.svg b/images/themes/classic/turn-cw.svg new file mode 100644 index 00000000..407b77f2 --- /dev/null +++ b/images/themes/classic/turn-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/dark/turn-ccw.svg b/images/themes/dark/turn-ccw.svg new file mode 100644 index 00000000..1d43ffcc --- /dev/null +++ b/images/themes/dark/turn-ccw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/images/themes/dark/turn-cw.svg b/images/themes/dark/turn-cw.svg new file mode 100644 index 00000000..407b77f2 --- /dev/null +++ b/images/themes/dark/turn-cw.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/mayo.pro b/mayo.pro index 1fd67082..770c796f 100644 --- a/mayo.pro +++ b/mayo.pro @@ -30,7 +30,7 @@ CONFIG(debug, debug|release) { DEFINES += \ QT_DISABLE_DEPRECATED_BEFORE=0x050F00 \ - QT_IMPLICIT_QFILEINFO_CONSTRUCTION + QT_IMPLICIT_QFILEINFO_CONSTRUCTION \ release_with_debuginfo:msvc { # https://docs.microsoft.com/en-us/cpp/build/reference/how-to-debug-a-release-build @@ -42,6 +42,10 @@ msvc { DEFINES += NOMINMAX QMAKE_CXXFLAGS += /we4150 # Deletion of pointer to incomplete type 'XXXX'; no destructor called QMAKE_CXXFLAGS += /std:c++17 + + greaterThan(QT_MAJOR_VERSION, 5) { + DEFINES += _USE_MATH_DEFINES + } } gcc|clang { QMAKE_CXXFLAGS += -std=c++17 @@ -49,14 +53,13 @@ gcc|clang { clang { # Silent Clang warnings about instantiation of variable 'Mayo::GenericProperty::TypeName' QMAKE_CXXFLAGS += -Wno-undefined-var-template -} -*clang-libc++* { # See https://libcxx.llvm.org/docs/UsingLibcxx.html - LIBS += -lc++fs + # LIBS += -lstdc++fs } macx { DEFINES += GL_SILENCE_DEPRECATION QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.15 + LIBS += -liconv # QMAKE_CXXFLAGS += -mmacosx-version-min=10.15 } win32 { @@ -69,22 +72,26 @@ INCLUDEPATH += \ HEADERS += \ $$files(src/base/*.h) \ - $$files(src/io_occ/*.h) \ $$files(src/io_dxf/*.h) \ $$files(src/io_image/*.h) \ + $$files(src/io_occ/*.h) \ + $$files(src/io_off/*.h) \ $$files(src/io_ply/*.h) \ $$files(src/graphics/*.h) \ $$files(src/gui/*.h) \ + $$files(src/measure/*.h) \ $$files(src/app/*.h) \ SOURCES += \ $$files(src/base/*.cpp) \ - $$files(src/io_occ/*.cpp) \ $$files(src/io_dxf/*.cpp) \ $$files(src/io_image/*.cpp) \ + $$files(src/io_occ/*.cpp) \ + $$files(src/io_off/*.cpp) \ $$files(src/io_ply/*.cpp) \ $$files(src/graphics/*.cpp) \ $$files(src/gui/*.cpp) \ + $$files(src/measure/*.cpp) \ $$files(src/app/*.cpp) \ \ src/3rdparty/fmt/src/format.cc \ @@ -148,6 +155,10 @@ LIBS += \ -lTKXmlXCAF \ -lTKXSBase \ +minOpenCascadeVersion(7, 7, 0) { + LIBS += -lTKXDE +} + # -- IGES support LIBS += -lTKIGES -lTKXDEIGES # -- STEP support @@ -173,6 +184,9 @@ minOpenCascadeVersion(7, 4, 0) { } # -- VRML support LIBS += -lTKVRML +!minOpenCascadeVersion(7, 7, 0) { + SOURCES -= src/io_occ/io_occ_vrml_reader.cpp +} # gmio isEmpty(GMIO_ROOT) { diff --git a/mayo.qrc b/mayo.qrc index d59ddd42..aecced61 100644 --- a/mayo.qrc +++ b/mayo.qrc @@ -78,5 +78,9 @@ images/themes/dark/reload.svg images/themes/classic/measure.svg images/themes/dark/measure.svg + images/themes/classic/turn-ccw.svg + images/themes/classic/turn-cw.svg + images/themes/dark/turn-ccw.svg + images/themes/dark/turn-cw.svg diff --git a/src/3rdparty/commit_kdbindings.txt b/src/3rdparty/commit_kdbindings.txt new file mode 100644 index 00000000..b4bb8816 --- /dev/null +++ b/src/3rdparty/commit_kdbindings.txt @@ -0,0 +1,2 @@ +055b3fa320e98516555e68f36d8529e24ee9d60e +Branch main \ No newline at end of file diff --git a/src/3rdparty/kdbindings/genindex_array.h b/src/3rdparty/kdbindings/genindex_array.h new file mode 100644 index 00000000..ab5cddfe --- /dev/null +++ b/src/3rdparty/kdbindings/genindex_array.h @@ -0,0 +1,207 @@ +/* +This code has been adapted from MIT licensed code, originally by Jeremy burns and available at +https://gist.github.com/jaburns/ca72487198832f6203e831133ffdfff4. +The original license is provided below: + +Copyright 2021 Jeremy Burns + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files +(the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace KDBindings { + +namespace Private { + +struct GenerationalIndex { + uint32_t index = 0; + uint32_t generation = 0; +}; + +class GenerationalIndexAllocator +{ + struct AllocatorEntry { + bool isLive = false; + uint32_t generation = 0; + }; + + std::vector m_entries; + std::vector m_freeIndices; + +public: + GenerationalIndex allocate() + { + if (m_freeIndices.size() > 0) { + uint32_t index = m_freeIndices.back(); + m_freeIndices.pop_back(); + + m_entries[index].generation += 1; + m_entries[index].isLive = true; + + return { index, m_entries[index].generation }; + } else { + // check that we are still within the bounds of uint32_t + if (m_entries.size() + 1 >= std::numeric_limits::max()) { + throw std::length_error(std::string("Maximum number of values inside GenerationalIndexArray reached: ") + std::to_string(m_entries.size())); + } + + m_entries.push_back({ true, 0 }); + return { static_cast(m_entries.size()) - 1, 0 }; + } + } + + bool deallocate(GenerationalIndex index) + { + if (isLive(index)) { + m_entries[index.index].isLive = false; + m_freeIndices.emplace_back(index.index); + return true; + } + + return false; + } + + bool isLive(GenerationalIndex index) const noexcept + { + return index.index < m_entries.size() && + m_entries[index.index].generation == index.generation && + m_entries[index.index].isLive; + } +}; + +// A GenerationalIndexArray stores elements in contiguous memory just like an std::vector +// and also allows items to be retrieved in constant time through indexed access, but it keeps +// track of the "version"/generation of values at indices so that it can inform an accessor +// when the item at the index it is trying to access is no longer the item that it wants. +template +class GenerationalIndexArray +{ + struct Entry { + uint32_t generation; + T value; + }; + + // TODO: m_entries never shrinks after an entry has been deleted, it might be + // a good idea to add a "trim" function at some point if this becomes an issue + + std::vector> m_entries; + GenerationalIndexAllocator m_allocator; + +public: + // Sets the value at a specific index inside the array + void set(const GenerationalIndex index, T &&value) + { + while (m_entries.size() <= index.index) + m_entries.emplace_back(std::nullopt); + +#ifndef NDEBUG + uint32_t previousGeneration = 0; + + const auto &previousEntry = m_entries[index.index]; + if (previousEntry) + previousGeneration = previousEntry->generation; + + assert(index.generation >= previousGeneration); +#endif + + m_entries[index.index] = std::optional{ { index.generation, std::move(value) } }; + } + + // Insert a value at the first free index and get the index back + GenerationalIndex insert(T &&value) + { + const auto index = m_allocator.allocate(); + set(index, std::move(value)); + return index; + } + + // Erase the value at the specified index and free up the index again + void erase(GenerationalIndex index) + { + if (m_allocator.deallocate(index)) + m_entries[index.index] = std::nullopt; + } + + // Get a pointer to the value at the specified index + T *get(GenerationalIndex index) + { + if (index.index >= m_entries.size()) + return nullptr; + + auto &entry = m_entries[index.index]; + if (entry && entry->generation == index.generation) { + return &entry->value; + } + + return nullptr; + } + + // Get a const pointer to the value at the specified index + const T *get(GenerationalIndex index) const noexcept + { + return const_cast(const_cast(this)->get(index)); + } + + // Erase all the values in the array and thus free up all indices too + void clear() + { + const auto numEntries = entriesSize(); + + for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) { + const auto index = indexAtEntry(i); + + if (index != std::nullopt) + erase(*index); + } + } + + // The number entries currently in the array, not all necessarily correspond to valid indices, + // use "indexAtEntry" to translate from an entry index to a optional GenerationalIndex + uint32_t entriesSize() const noexcept + { + // this cast is safe because the allocator checks that we never exceed the capacity of uint32_t + return static_cast(m_entries.size()); + } + + // Convert an entry index into a GenerationalIndex, if possible otherwise returns nullopt + std::optional indexAtEntry(uint32_t entryIndex) const + { + if (entryIndex >= entriesSize()) + return std::nullopt; + + const auto &entry = m_entries[entryIndex]; + if (!entry) + return std::nullopt; + + GenerationalIndex index = { entryIndex, entry->generation }; + + if (m_allocator.isLive(index)) + return index; + + return std::nullopt; + } +}; + +} //namespace Private + +} // namespace KDBindings diff --git a/src/3rdparty/kdbindings/signal.h b/src/3rdparty/kdbindings/signal.h new file mode 100644 index 00000000..c141ee23 --- /dev/null +++ b/src/3rdparty/kdbindings/signal.h @@ -0,0 +1,720 @@ +/* + This file is part of KDBindings. + + SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Sean Harmer + + SPDX-License-Identifier: MIT + + Contact KDAB at for commercial licensing options. +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/** + * @brief The main namespace of the KDBindings library. + * + * All public parts of KDBindings are members of this namespace. + */ +namespace KDBindings { + +template +class Signal; + +namespace Private { +// +// This class defines a virtual interface, that the Signal this ConnectionHandle refers +// to must implement. +// It allows ConnectionHandle to refer to this non-template class, which then dispatches +// to the template implementation using virtual function calls. +// It allows ConnectionHandle to be a non-template class. +class SignalImplBase +{ +public: + SignalImplBase() = default; + + virtual ~SignalImplBase() = default; + + virtual void disconnect(const GenerationalIndex &id) = 0; + virtual bool blockConnection(const GenerationalIndex &id, bool blocked) = 0; + virtual bool isConnectionActive(const GenerationalIndex &id) const = 0; + virtual bool isConnectionBlocked(const GenerationalIndex &id) const = 0; +}; + +} // namespace Private + +/** + * @brief A ConnectionHandle represents the connection of a Signal + * to a slot (i.e. a function that is called when the Signal is emitted). + * + * It is returned from a Signal when a connection is created and used to + * manage the connection by disconnecting, (un)blocking it and checking its state. + * + * To make sure a Connection to an object is disconnected correctly, consider + * storing a ScopedConnection to its ConnectionHandle inside the object. + **/ +class ConnectionHandle +{ +public: + /** + * A ConnectionHandle can be default constructed. + * In this case the ConnectionHandle will not reference any active connection (i.e. isActive() will return false), + * and not belong to any Signal. + **/ + ConnectionHandle() = default; + + /** + * A ConnectionHandle can be copied. + **/ + ConnectionHandle(const ConnectionHandle &) = default; + ConnectionHandle &operator=(const ConnectionHandle &) = default; + + /** + * A ConnectionHandle can be moved. + **/ + ConnectionHandle(ConnectionHandle &&) = default; + ConnectionHandle &operator=(ConnectionHandle &&) = default; + + /** + * Disconnect the slot. + * + * When this function is called, the function that was passed to Signal::connect + * to create this ConnectionHandle will no longer be called when the Signal is emitted. + * + * If the ConnectionHandle is not active or the connection has already been disconnected, + * nothing happens. + * + * After this call, the ConnectionHandle will be inactive (i.e. isActive() returns false) + * and will no longer belong to any Signal (i.e. belongsTo returns false). + **/ + void disconnect() + { + if (auto shared_impl = checkedLock()) { + shared_impl->disconnect(m_id); + } + // ConnectionHandle is no longer active; + m_signalImpl.reset(); + } + + /** + * Check whether the connection of this ConnectionHandle is active. + * + * @return true if the ConnectionHandle refers to an active Signal + * and the connection was not disconnected previously, false otherwise. + **/ + bool isActive() const + { + return static_cast(checkedLock()); + } + + /** + * Sets the block state of the connection. + * If a connection is blocked, emitting the Signal will no longer call this + * connections slot, until the connection is unblocked. + * + * Behaves the same as calling Signal::blockConnection with this + * ConnectionHandle as argument. + * + * To temporarily block a connection, consider using an instance of ConnectionBlocker, + * which offers a RAII-style implementation that makes sure the connection is always + * returned to its original state. + * + * @param blocked The new blocked state of the connection. + * @return whether the connection was previously blocked. + * @throw std::out_of_range Throws if the connection is not active (i.e. isActive() returns false). + **/ + bool block(bool blocked) + { + if (auto shared_impl = checkedLock()) { + return shared_impl->blockConnection(m_id, blocked); + } + throw std::out_of_range("Cannot block a non-active connection!"); + } + + /** + * Checks whether the connection is currently blocked. + * + * To change the blocked state of a connection, call ConnectionHandle::block. + * + * @return whether the connection is currently blocked. + **/ + bool isBlocked() const + { + if (auto shared_impl = checkedLock()) { + return shared_impl->isConnectionBlocked(m_id); + } + throw std::out_of_range("Cannot check whether a non-active connection is blocked!"); + } + + /** + * Check whether this ConnectionHandle belongs to the given Signal. + * + * @return true if this ConnectionHandle refers to a connection within the given Signal + **/ + template + bool belongsTo(const Signal &signal) const + { + auto shared_impl = m_signalImpl.lock(); + return shared_impl && shared_impl == std::static_pointer_cast(signal.m_impl); + } + +private: + template + friend class Signal; + + std::weak_ptr m_signalImpl; + Private::GenerationalIndex m_id; + + // private, so it is only available from Signal + ConnectionHandle(std::weak_ptr signalImpl, Private::GenerationalIndex id) + : m_signalImpl{ std::move(signalImpl) }, m_id{ std::move(id) } + { + } + + // Checks that the weak_ptr can be locked and that the connection is + // still active + std::shared_ptr checkedLock() const + { + auto shared_impl = m_signalImpl.lock(); + if (shared_impl && shared_impl->isConnectionActive(m_id)) { + return shared_impl; + } + return nullptr; + } +}; + +/** + * @brief A Signal provides a mechanism for communication between objects. + * + * KDBindings::Signal recreates the Qt's Signals & Slots mechanism in pure C++17. + * A Signal can be used to notify any number of slots that a certain event has occurred. + * + * The slot can be almost any callable object, including member functions and lambdas. + * + * This connection happens in a type-safe manner, as a slot can only be connected to + * a Signal when the arguments of the slot match the values the Signal emits. + * + * The Args type parameter pack describe which value types the Signal will emit. + * + * Examples: + * - @ref 01-simple-connection/main.cpp + * - @ref 02-signal-member/main.cpp + * - @ref 03-member-arguments/main.cpp + * - @ref 07-advanced-connections/main.cpp + */ +template +class Signal +{ + static_assert( + std::conjunction>...>::value, + "R-value references are not allowed as Signal parameters!"); + + // The Signal::Impl class exists, so Signals can be implemented in a PIMPL-like way. + // This allows us to easily move Signals without losing their ConnectionHandles, as well as + // making an unconnected Signal only sizeof(shared_ptr). + class Impl : public Private::SignalImplBase + { + public: + Impl() noexcept { } + + ~Impl() noexcept { } + + // Signal::Impls are not copyable + Impl(Impl const &other) = delete; + Impl &operator=(Impl const &other) = delete; + + // Signal::Impls are not moveable, this would break the ConnectionHandles + Impl(Impl &&other) = delete; + Impl &operator=(Impl &&other) = delete; + + // Connects a std::function to the signal. The returned + // value can be used to disconnect the function again. + Private::GenerationalIndex connect(std::function const &slot) + { + return m_connections.insert({ slot }); + } + + // Disconnects a previously connected function + void disconnect(const Private::GenerationalIndex &id) override + { + m_connections.erase(id); + } + + // Disconnects all previously connected functions + void disconnectAll() + { + m_connections.clear(); + } + + bool blockConnection(const Private::GenerationalIndex &id, bool blocked) override + { + Connection *connection = m_connections.get(id); + if (connection) { + const bool wasBlocked = connection->blocked; + connection->blocked = blocked; + return wasBlocked; + } else { + throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!"); + } + } + + bool isConnectionActive(const Private::GenerationalIndex &id) const override + { + return m_connections.get(id); + } + + bool isConnectionBlocked(const Private::GenerationalIndex &id) const override + { + auto connection = m_connections.get(id); + if (connection) { + return connection->blocked; + } else { + throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!"); + } + } + + // Calls all connected functions + void emit(Args... p) const + { + const auto numEntries = m_connections.entriesSize(); + + // This loop can tolerate signal handles being disconnected inside a slot, + // but adding new connections to a signal inside a slot will still be undefined behaviour + for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) { + const auto index = m_connections.indexAtEntry(i); + + if (index) { + const auto con = m_connections.get(*index); + + if (!con->blocked) + con->slot(p...); + } + } + } + + private: + struct Connection { + std::function slot; + bool blocked{ false }; + }; + mutable Private::GenerationalIndexArray m_connections; + }; + +public: + /** Signals are default constructible */ + Signal() = default; + + /** + * Signals cannot be copied. + **/ + Signal(const Signal &) = delete; + Signal &operator=(Signal const &other) = delete; + + /** Signals can be moved */ + Signal(Signal &&other) noexcept = default; + Signal &operator=(Signal &&other) noexcept = default; + + /** + * A signal disconnects all slots when it is destructed + * + * Therefore, all active ConnectionHandles that belonged to this Signal + * will no longer be active (i.e. ConnectionHandle::isActive will return false). + */ + ~Signal() + { + disconnectAll(); + } + + /** + * Connects a std::function to the signal. + * + * When emit() is called on the Signal, the functions will be called with + * the arguments provided to emit(). + * + * @return An instance of ConnectionHandle, that can be used to disconnect + * or temporarily block the connection. + */ + ConnectionHandle connect(std::function const &slot) + { + ensureImpl(); + + return ConnectionHandle{ m_impl, m_impl->connect(slot) }; + } + + /** + * A template overload of Signal::connect that makes it easier to connect arbitrary functions to this + * Signal. + * It connects a function to this Signal, binds any provided arguments to that function and discards + * any values emitted by this Signal that aren't needed by the resulting function. + * + * This is especially useful for connecting member functions to signals. + * + * Examples: + * @code + * Signal signal; + * std::vector numbers{ 1, 2, 3 }; + * bool emitted = false; + * + * // disambiguation necessary, as push_back is overloaded. + * void (std::vector::*push_back)(const int &) = &std::vector::push_back; + * signal.connect(push_back, &numbers); + * + * // this slot doesn't require the int argument, so it will be discarded. + * signal.connect([&emitted]() { emitted = true; }); + * + * signal.emit(4); // Will add 4 to the vector and set emitted to true + * @endcode + * + * For more examples see the @ref 07-advanced-connections/main.cpp example. + * + * @return An instance of a Signal::ConnectionHandle that refers to this connection. + * Warning: When connecting a member function you must use the returned ConnectionHandle + * to disconnect when the object containing the slot goes out of scope! + **/ + // The enable_if_t makes sure that this connect function specialization is only + // available if we provide a function that cannot be otherwise converted to a + // std::function, as it otherwise tries to take precedence + // over the normal connect function. + template>>, std::integral_constant>>> + ConnectionHandle connect(Func &&slot, FuncArgs &&...args) + { + std::function bound = Private::bind_first(std::forward(slot), std::forward(args)...); + return connect(bound); + } + + /** + * Disconnect a previously connected slot. + * + * After the slot was successfully disconnected, the ConnectionHandle will no + * longer be active. (i.e. ConnectionHandle::isActive will return false). + * + * @throw std::out_of_range - If the ConnectionHandle does not belong to this + * Signal (i.e. ConnectionHandle::belongsTo returns false). + */ + void disconnect(const ConnectionHandle &handle) + { + if (m_impl && handle.belongsTo(*this)) { + m_impl->disconnect(handle.m_id); + // TODO check if Impl is now empty and reset + } else { + throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!"); + } + } + + /** + * Disconnect all previously connected functions. + * + * All currently active ConnectionHandles that belong to this Signal will no + * longer be active afterwards. (i.e. ConnectionHandle::isActive will return false). + */ + void disconnectAll() + { + if (m_impl) { + m_impl->disconnectAll(); + // Once all connections are disconnected, we can release ownership of the Impl. + // This does not destroy the Signal itself, just the Impl object. + // If another slot is connected, another Impl object will be constructed. + m_impl.reset(); + } + // If m_impl is nullptr, we don't have any connections to disconnect + } + + /** + * Sets the block state of the connection. + * If a connection is blocked, emitting the Signal will no longer call this + * connections slot, until the connection is unblocked. + * + * ConnectionHandle::block can be used as an alternative. + * + * To temporarily block a connection, consider using an instance of ConnectionBlocker, + * which offers a RAII-style implementation that makes sure the connection is always + * returned to its original state. + * + * @param blocked Whether the connection should be blocked from now on. + * @param handle The ConnectionHandle to block. + * @return Whether the connection was previously blocked. + * @throw std::out_of_range - If the ConnectionHandle does not belong to this + * Signal (i.e. ConnectionHandle::belongsTo returns false). + */ + bool blockConnection(const ConnectionHandle &handle, bool blocked) + { + if (m_impl && handle.belongsTo(*this)) { + return m_impl->blockConnection(handle.m_id, blocked); + } else { + throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!"); + } + } + + /** + * Checks whether the connection is currently blocked. + * + * To change the blocked state of a connection, call blockConnection(). + * + * @return Whether the connection is currently blocked + * @throw std::out_of_range - If the ConnectionHandle does not belong to this + * Signal (i.e. ConnectionHandle::belongsTo returns false). + */ + bool isConnectionBlocked(const ConnectionHandle &handle) const + { + assert(handle.belongsTo(*this)); + if (!m_impl) { + throw std::out_of_range("Provided ConnectionHandle does not match any connection\nLikely the connection was deleted before!"); + } + + return m_impl->isConnectionBlocked(handle.m_id); + } + + /** + * Emits the Signal, which causes all connected slots to be called, + * as long as they are not blocked. + * + * The arguments provided to emit will be passed to each slot by copy, + * therefore consider using (const) references as the Args to the Signal + * wherever possible. + * + * Note: Slots may disconnect themselves during an emit, however it is + * undefined whether a slot that is connected during the emit function + * of the Signal will also be called during this emit, or only at the next + * emit. + */ + void emit(Args... p) const + { + if (m_impl) + m_impl->emit(p...); + + // if m_impl is nullptr, we don't have any slots connected, don't bother emitting + } + +private: + friend class ConnectionHandle; + + void ensureImpl() + { + if (!m_impl) { + m_impl = std::make_shared(); + } + } + + // shared_ptr is used here instead of unique_ptr, so ConnectionHandle instances can + // use a weak_ptr to check if the Signal::Impl they reference is still alive. + // + // This makes Signals easily copyable in theory, but the semantics of this are unclear. + // Copying could either simply copy the shared_ptr, which means the copy would share + // the connections of the original, which is possibly unintuitive, or the Impl would + // have to be copied as well. + // This would however leave connections without handles to disconnect them. + // So copying is forbidden for now. + // + // Think of this shared_ptr more like a unique_ptr with additional weak_ptr's + // in ConnectionHandle that can check whether the Impl object is still alive. + mutable std::shared_ptr m_impl; +}; + +/** + * @brief A ScopedConnection is a RAII-style way to make sure a Connection is disconnected. + * + * When the ScopedConnections scope ends, the connection this ScopedConnection guards will be disconnected. + * + * Example: + * - @ref 08-managing-connections/main.cpp + */ +class ScopedConnection +{ +public: + /** + * @brief A ScopedConnection can be default constructed + * + * A default constructed ScopedConnection has no connection to guard. + * Therefore it does nothing when it is destructed, unless a ConnectionHandle is assigned to it. + */ + ScopedConnection() = default; + + /** A ScopedConnection can be move constructed */ + ScopedConnection(ScopedConnection &&) = default; + + /** A ScopedConnection cannot be copied */ + ScopedConnection(const ScopedConnection &) = delete; + /** A ScopedConnection cannot be copied */ + ScopedConnection &operator=(const ScopedConnection &) = delete; + + /** A ScopedConnection can be move assigned */ + ScopedConnection &operator=(ScopedConnection &&other) + { + m_connection.disconnect(); + m_connection = std::move(other.m_connection); + return *this; + } + + /** + * A ScopedConnection can be constructed from a ConnectionHandle + */ + ScopedConnection(ConnectionHandle &&h) + : m_connection(std::move(h)) + { + } + + /** + * A ScopedConnection can be assigned from a ConnectionHandle + */ + ScopedConnection &operator=(ConnectionHandle &&h) + { + return *this = ScopedConnection(std::move(h)); + } + + /** + * @return the handle to the connection this instance is managing + */ + ConnectionHandle &handle() + { + return m_connection; + } + + /** + * @overload + */ + const ConnectionHandle &handle() const + { + return m_connection; + } + + /** + * Convenience access to the underlying ConnectionHandle using the `->` operator. + */ + ConnectionHandle *operator->() + { + return &m_connection; + } + + /** + * @overload + */ + const ConnectionHandle *operator->() const + { + return &m_connection; + } + + /** + * When a ConnectionHandle is destructed it disconnects the connection it guards. + */ + ~ScopedConnection() + { + m_connection.disconnect(); + } + +private: + ConnectionHandle m_connection; +}; + +/** + * @brief A ConnectionBlocker is a convenient RAII-style mechanism for temporarily blocking a connection. + * + * When a ConnectionBlocker is constructed, it will block the connection. + * + * When it is destructed, it will return the connection to the blocked state it was in + * before the ConnectionBlocker was constructed. + * + * Example: + * - @ref 08-managing-connections/main.cpp + */ +class ConnectionBlocker +{ +public: + /** + * Constructs a new ConnectionBlocker and blocks the connection this ConnectionHandle + * refers to. + * + * @throw std::out_of_range If the connection is not active (i.e. ConnectionHandle::isActive() returns false). + */ + explicit ConnectionBlocker(const ConnectionHandle &handle) + : m_handle{ handle } + { + m_wasBlocked = m_handle.block(true); + } + + /** + * Destructs the ConnectionBlocker and returns the connection into the blocked state it was in + * before the ConnectionBlocker was constructed. + */ + ~ConnectionBlocker() + { + m_handle.block(m_wasBlocked); + } + +private: + ConnectionHandle m_handle; + bool m_wasBlocked{ false }; +}; + +/** + * @example 01-simple-connection/main.cpp + * + * A simple example of how to create a KDBindings::Signal and connect a lambda to it. + * + * The output of this example is: + * ``` + * The answer: 42 + * ``` + */ + +/** + * @example 02-signal-member/main.cpp + * + * An example of how to connect a member function to a KDBindings::Signal. + * + * The output of this example is: + * ``` + * Hello World! + * ``` + */ + +/** + * @example 03-member-arguments/main.cpp + * + * An example of how to connect a member function with arguments to a KDBindings::Signal. + * + * The output of this example is: + * ``` + * Bob received: Have a nice day! + * Alice received: Thank you! + * ``` + */ + +/** + * @example 07-advanced-connections/main.cpp + * + * An example of how to use the KDBindings::Signal::connect() overloaded function for advanced slot connections. + * + * The output of this example is: + * ``` + * Hello World! + * Emitted value: 5 + * true + * ``` + */ + +/** + * @example 08-managing-connections/main.cpp + * + * An example of how to use a ScopedConnection and ConnectionBlocker to manage + * when a Connection is disconnected or blocked. + * + * Expected output: + * ``` + * Guard is connected: 1 + * Connection is not blocked: 3 + * Connection is not blocked: 5 + * ``` + */ + +} // namespace KDBindings diff --git a/src/3rdparty/kdbindings/utils.h b/src/3rdparty/kdbindings/utils.h new file mode 100644 index 00000000..f9ef8643 --- /dev/null +++ b/src/3rdparty/kdbindings/utils.h @@ -0,0 +1,169 @@ +/* + This file is part of KDBindings. + + SPDX-FileCopyrightText: 2021-2022 Klarälvdalens Datakonsult AB, a KDAB Group company + Author: Leon Matthes + + SPDX-License-Identifier: MIT + + Contact KDAB at for commercial licensing options. +*/ +#pragma once + +#include +#include +#include + +namespace KDBindings { + +/** +* The contents of this namespace may only be accessed by the implementation of KDBindings, they +* are not part of KDBindings public API and may be altered at any time and provide no guarantees +* of any kind when used directly. +**/ +namespace Private { + +// ------------------------ get_arity -------------------------- +// get_arity is a template function that returns the number of arguments +// of (almost) any callable object. +// The easiest way is to simply call get_arity() for callable type T. +// It needs to be constexpr in order so it can be used in template arguments. + +// To overload get_arity, it needs a marker type, as C++ doesn't allow partial +// function specialization. +template +struct TypeMarker { + constexpr TypeMarker() = default; +}; + +// base implementation of get_arity refers to specialized implementations for each +// type of callable object by using the overload for its specialized TypeMarker. +template +constexpr size_t get_arity() +{ + return get_arity(TypeMarker>{}); +} + +// Syntactic sugar version of get_arity, allows to pass any callable object +// to get_arity, instead of having to pass its decltype as a template argument. +template +constexpr size_t get_arity(const T &) +{ + return get_arity(); +} + +// The arity of a function pointer is simply its number of arguments. +template +constexpr size_t get_arity(TypeMarker) +{ + return sizeof...(Arguments); +} + +template +constexpr size_t get_arity(TypeMarker) +{ + return sizeof...(Arguments); +} + +// The arity of a generic callable object is the arity of its operator() - 1, as the this +// pointer is already known for such an object. +template +constexpr size_t get_arity(TypeMarker) +{ + return get_arity(TypeMarker{}) - 1; +} + +// Macro to help define most combinations of possible member function qualifiers. +// Add + 1 to sizeof...(Arguments) here as the "this" pointer is an implicit argument to any member function. +#define KDBINDINGS_DEFINE_MEMBER_GET_ARITY(MODIFIERS) \ + template \ + constexpr size_t get_arity(::KDBindings::Private::TypeMarker) \ + { \ + return sizeof...(Arguments) + 1; \ + } + +// Define the get_arity version without modifiers without using the macro. +// MSVC otherwise complains about a call to the macro with too few arguments +template +constexpr size_t get_arity(::KDBindings::Private::TypeMarker) +{ + return sizeof...(Arguments) + 1; +} + +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&) + +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&) + +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&noexcept) + +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&noexcept) +KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&noexcept) + +// -------------------- placeholder and bind_first --------------------- +// Inspired by https://gist.github.com/engelmarkus/fc1678adbed1b630584c90219f77eb48 +// A placeholder provides a way to construct something equivalent to a std::placeholders::_N +// with N as a template argument. +// +// Note: As placeholders start at 1, therefore placeholder<0> is NOT a valid placeholder. +template +struct placeholder { +}; + +template +auto bind_first_helper(std::index_sequence, Func &&fun, Args... args) +{ + return std::bind(std::forward(fun), std::forward(args)..., placeholder{}...); +} + +// bind_first binds the first arguments to the callable object (i.e. function) to the values provided by args. +// The return value is a new function taking get_arity - sizeof...(Args) many arguments, with the first +// sizeof...(Args) arguments bound to the values of args. +// This is different to a call with std::bind(fun, args...), as the callable object created by std::bind would +// in this case now take zero arguments, whilst bind_first still expects the remaining arguments to be provided +// +// For now, providing instances of std::placeholders in Args is not allowed, as the implications of this are +// unclear if sizeof...(Args) != get_arity. The enable_if_t makes sure none of the Args value is a placeholder. +// +// In the future, we could provide another overload of this function that allows placeholders, as long as all arguments +// are bound. +template< + typename Func, + typename... Args, + /*Disallow any placeholder arguments, they would mess with the number and ordering of required and bound arguments, and are, for now, unsupported*/ + typename = std::enable_if_t>...>>> +auto bind_first(Func &&fun, Args &&...args) +{ + return bind_first_helper(std::make_index_sequence() - sizeof...(Args)>{}, std::forward(fun), std::forward(args)...); +} + +} // namespace Private + +} // namespace KDBindings + +namespace std { + +// This allows a placeholder to be used as a replacement of a std::placeholders. +template +struct is_placeholder> + : integral_constant { +}; + +} // namespace std diff --git a/src/app/app_context.cpp b/src/app/app_context.cpp new file mode 100644 index 00000000..a4778116 --- /dev/null +++ b/src/app/app_context.cpp @@ -0,0 +1,129 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "app_context.h" + +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "widget_gui_document.h" + +namespace Mayo { + +AppContext::AppContext(MainWindow* wnd) + : m_wnd(wnd) +{ + QObject::connect( + m_wnd->m_ui->combo_GuiDocuments, qOverload(&QComboBox::currentIndexChanged), + this, &AppContext::onCurrentDocumentIndexChanged + ); +} + +GuiApplication* AppContext::guiApp() const +{ + return m_wnd->m_guiApp; +} + +TaskManager* AppContext::taskMgr() const +{ + return &m_wnd->m_taskMgr; +} + +QWidget* AppContext::widgetMain() const +{ + return m_wnd; +} + +QWidget* AppContext::widgetLeftSidebar() const +{ + return m_wnd->m_ui->widget_Left; +} + +IAppContext::ModeWidgetMain AppContext::modeWidgetMain() const +{ + auto widget = m_wnd->m_ui->stack_Main->currentWidget(); + if (widget == m_wnd->m_ui->page_MainHome) + return ModeWidgetMain::Home; + else if (widget == m_wnd->m_ui->page_MainControl) + return ModeWidgetMain::Documents; + + return ModeWidgetMain::Unknown; +} + +V3dViewController* AppContext::v3dViewController(const GuiDocument* guiDoc) const +{ + auto widgetDoc = this->findWidgetGuiDocument([=](WidgetGuiDocument* candidate) { + return candidate->guiDocument() == guiDoc; + }); + return widgetDoc ? widgetDoc->controller() : nullptr; +} + +int AppContext::findDocumentIndex(Document::Identifier docId) const +{ + int index = -1; + auto widgetDoc = this->findWidgetGuiDocument([&](WidgetGuiDocument* candidate) { + ++index; + return candidate->documentIdentifier() == docId; + }); + return widgetDoc ? index : -1; +} + +Document::Identifier AppContext::findDocumentFromIndex(int index) const +{ + auto widgetDoc = m_wnd->widgetGuiDocument(index); + return widgetDoc ? widgetDoc->documentIdentifier() : -1; +} + +Document::Identifier AppContext::currentDocument() const +{ + const int index = m_wnd->m_ui->combo_GuiDocuments->currentIndex(); + auto widgetDoc = m_wnd->widgetGuiDocument(index); + return widgetDoc ? widgetDoc->documentIdentifier() : -1; +} + +void AppContext::setCurrentDocument(Document::Identifier docId) +{ + auto widgetDoc = this->findWidgetGuiDocument([=](WidgetGuiDocument* widgetDoc) { + return widgetDoc->documentIdentifier() == docId; + }); + const int docIndex = m_wnd->m_ui->stack_GuiDocuments->indexOf(widgetDoc); + m_wnd->m_ui->combo_GuiDocuments->setCurrentIndex(docIndex); +} + +void AppContext::updateControlsEnabledStatus() +{ + m_wnd->updateControlsActivation(); +} + +void AppContext::deleteDocumentWidget(const DocumentPtr& doc) +{ + QWidget* widgetDoc = this->findWidgetGuiDocument([&](WidgetGuiDocument* widgetDoc) { + return widgetDoc->documentIdentifier() == doc->identifier(); + }); + if (widgetDoc) { + m_wnd->m_ui->stack_GuiDocuments->removeWidget(widgetDoc); + widgetDoc->deleteLater(); + } +} + +WidgetGuiDocument* AppContext::findWidgetGuiDocument(std::function fn) const +{ + const int widgetCount = m_wnd->m_ui->stack_GuiDocuments->count(); + for (int i = 0; i < widgetCount; ++i) { + auto candidate = m_wnd->widgetGuiDocument(i); + if (candidate && fn(candidate)) + return candidate; + } + + return nullptr; +} + +void AppContext::onCurrentDocumentIndexChanged(int docIndex) +{ + auto widgetDoc = m_wnd->widgetGuiDocument(docIndex); + emit this->currentDocumentChanged(widgetDoc ? widgetDoc->documentIdentifier() : -1); +} + +} // namespace Mayo diff --git a/src/app/app_context.h b/src/app/app_context.h new file mode 100644 index 00000000..583ef5e4 --- /dev/null +++ b/src/app/app_context.h @@ -0,0 +1,45 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "commands_api.h" + +namespace Mayo { + +class MainWindow; +class WidgetGuiDocument; + +// Provides implementation of IAppContext based on MainWindow +class AppContext : public IAppContext { +public: + AppContext(MainWindow* wnd); + + GuiApplication* guiApp() const override; + TaskManager* taskMgr() const override; + QWidget* widgetMain() const override; + QWidget* widgetLeftSidebar() const override; + ModeWidgetMain modeWidgetMain() const override; + V3dViewController* v3dViewController(const GuiDocument* guiDoc) const override; + + int findDocumentIndex(Document::Identifier docId) const override; + Document::Identifier findDocumentFromIndex(int index) const override; + + Document::Identifier currentDocument() const override; + void setCurrentDocument(Document::Identifier docId) override; + + void updateControlsEnabledStatus() override; + void deleteDocumentWidget(const DocumentPtr& doc) override; + +private: + WidgetGuiDocument* findWidgetGuiDocument(std::function fn) const; + + void onCurrentDocumentIndexChanged(int docIndex); + + MainWindow* m_wnd = nullptr; +}; + +} // namespace Mayo diff --git a/src/app/app_module.cpp b/src/app/app_module.cpp index 5a33e23d..46e87e49 100644 --- a/src/app/app_module.cpp +++ b/src/app/app_module.cpp @@ -31,9 +31,10 @@ namespace Mayo { AppModule::AppModule() - : m_settings(new Settings(this)), + : m_settings(new Settings), m_props(m_settings), - m_locale(QLocale::system()) + m_stdLocale(std::locale("")), + m_qtLocale(QLocale::system()) { static bool metaTypesRegistered = false; if (!metaTypesRegistered) { @@ -47,15 +48,20 @@ AppModule::AppModule() QStringUtils::TextOptions AppModule::defaultTextOptions() const { QStringUtils::TextOptions opts; - opts.locale = this->locale(); + opts.locale = this->qtLocale(); opts.unitDecimals = m_props.unitSystemDecimals; opts.unitSchema = m_props.unitSystemSchema; return opts; } -const QLocale& AppModule::locale() const +const std::locale& AppModule::stdLocale() const { - return m_locale; + return m_stdLocale; +} + +const QLocale& AppModule::qtLocale() const +{ + return m_qtLocale; } const Enumeration& AppModule::languages() @@ -135,22 +141,23 @@ bool AppModule::fromVariant(Property* prop, const Settings::Variant& variant) co void AppModule::emitMessage(Messenger::MessageType msgType, std::string_view text) { + const QString qtext = to_QString(text); { - std::lock_guard lock(m_mutexMessageLog); - m_messageLog.push_back({ msgType, to_QString(text) }); + [[maybe_unused]] std::lock_guard lock(m_mutexMessageLog); + m_messageLog.push_back({ msgType, qtext }); } - emit this->message(msgType, to_QString(text)); + this->signalMessage.send(msgType, qtext); } void AppModule::clearMessageLog() { { - std::lock_guard lock(m_mutexMessageLog); + [[maybe_unused]] std::lock_guard lock(m_mutexMessageLog); m_messageLog.clear(); } - emit this->messageLogCleared(); + this->signalMessageLogCleared.send(); } void AppModule::prependRecentFile(const FilePath& fp) @@ -334,4 +341,10 @@ AppModule* AppModule::get() return &appModule; } +AppModule::~AppModule() +{ + delete m_settings; + m_settings = nullptr; +} + } // namespace Mayo diff --git a/src/app/app_module.h b/src/app/app_module.h index f96dbd20..5fadb7a7 100644 --- a/src/app/app_module.h +++ b/src/app/app_module.h @@ -18,7 +18,7 @@ #include "../base/settings.h" #include "../base/unit_system.h" -#include +#include #include class TDF_Label; @@ -33,12 +33,10 @@ class TaskProgress; // Provides the root application object as a singleton // Implements also the behavior specific to the application class AppModule : - public QObject, public IO::ParametersProvider, public PropertyValueConversion, public Messenger { - Q_OBJECT MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::AppModule) public: // Loggable message @@ -50,6 +48,8 @@ class AppModule : // Query singleton instance static AppModule* get(); + ~AppModule(); + // Settings const AppModuleProperties* properties() const { return &m_props; } AppModuleProperties* properties() { return &m_props; } @@ -62,7 +62,8 @@ class AppModule : QStringUtils::TextOptions defaultTextOptions() const; // Current locale used by the application - const QLocale& locale() const; + const std::locale& stdLocale() const; + const QLocale& qtLocale() const; // Available supported languages static const Enumeration& languages(); @@ -73,8 +74,8 @@ class AppModule : // Logging void clearMessageLog(); Span messageLog() const { return m_messageLog; } - Q_SIGNAL void message(Messenger::MessageType msgType, const QString& text); - Q_SIGNAL void messageLogCleared(); + Signal signalMessage; + Signal<> signalMessageLogCleared; // Recent files void prependRecentFile(const FilePath& fp); @@ -117,7 +118,8 @@ class AppModule : AppModuleProperties m_props; std::vector m_messageLog; std::mutex m_mutexMessageLog; - QLocale m_locale; + std::locale m_stdLocale; + QLocale m_qtLocale; std::vector> m_vecDocTreeNodePropsProvider; }; diff --git a/src/app/app_module_properties.cpp b/src/app/app_module_properties.cpp index 823b9927..a66b597f 100644 --- a/src/app/app_module_properties.cpp +++ b/src/app/app_module_properties.cpp @@ -46,6 +46,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) settings->addSetting(&this->lastOpenDir, groupId_application); settings->addSetting(&this->lastSelectedFormatFilter, groupId_application); settings->addSetting(&this->linkWithDocumentSelector, groupId_application); + settings->addSetting(&this->forceOpenGlFallbackWidget, groupId_application); this->recentFiles.setUserVisible(false); this->lastOpenDir.setUserVisible(false); this->lastSelectedFormatFilter.setUserVisible(false); @@ -61,6 +62,7 @@ AppModuleProperties::AppModuleProperties(Settings* settings) settings->addSetting(&this->navigationStyle, groupId_graphics); settings->addSetting(&this->defaultShowOriginTrihedron, groupId_graphics); settings->addSetting(&this->instantZoomFactor, groupId_graphics); + settings->addSetting(&this->turnViewAngleIncrement, groupId_graphics); // -- Clip planes settings->addSetting(&this->clipPlanesCappingOn, sectionId_graphicsClipPlanes); settings->addSetting(&this->clipPlanesCappingHatchOn, sectionId_graphicsClipPlanes); @@ -82,11 +84,17 @@ AppModuleProperties::AppModuleProperties(Settings* settings) this->lastOpenDir.setValue({}); this->lastSelectedFormatFilter.setValue({}); this->linkWithDocumentSelector.setValue(true); +#ifndef MAYO_OS_MAC + this->forceOpenGlFallbackWidget.setValue(false); +#else + this->forceOpenGlFallbackWidget.setValue(true); +#endif }); settings->addResetFunction(groupId_graphics, [=]{ this->navigationStyle.setValue(WidgetOccViewController::NavigationStyle::Mayo); this->defaultShowOriginTrihedron.setValue(true); this->instantZoomFactor.setValue(5.); + this->turnViewAngleIncrement.setQuantity(5 * Quantity_Degree); }); settings->addResetFunction(groupId_meshing, [&]{ this->meshingQuality.setValue(BRepMeshQuality::Normal); @@ -147,26 +155,47 @@ void AppModuleProperties::IO_bindParameters(const IO::System* ioSystem) void AppModuleProperties::retranslate() { + // 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")); + this->forceOpenGlFallbackWidget.setDescription( + textIdTr("Force usage of the fallback Qt widget to display OpenGL graphics.\n\n" + "When `OFF` the application will try to use OpenGL framebuffer for rendering, " + "this allows to display overlay widgets(eg measure tools panel) with translucid " + "background. " + "However using OpenGL framebuffer might cause troubles for some users(eg empty " + "3D window) especially on macOS.\n\n" + "When `ON` the application will use a regular Qt widget for rendering which " + "proved to be more supported.\n\n" + "This option is applicable when OpenCascade ≥ 7.6 version. " + "Change will take effect after application restart") + ); + + // Meshing 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 " + textIdTr("For the tessellation 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 " + textIdTr("For the tessellation 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.")); + + // Graphics this->navigationStyle.setDescription( textIdTr("3D view manipulation shortcuts configuration to mimic other common CAD applications")); + this->turnViewAngleIncrement.setDescription( + textIdTr("Angle increment used to turn(rotate) the 3D view around the normal of the view plane(Z axis frame reference)")); + + // -- Graphics/ClipPlanes 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")); diff --git a/src/app/app_module_properties.h b/src/app/app_module_properties.h index 267632a3..d8874672 100644 --- a/src/app/app_module_properties.h +++ b/src/app/app_module_properties.h @@ -52,6 +52,7 @@ class AppModuleProperties : public PropertyGroup { PropertyFilePath lastOpenDir{ this, textId("lastOpenFolder") }; PropertyString lastSelectedFormatFilter{ this, textId("lastSelectedFormatFilter") }; PropertyBool linkWithDocumentSelector{ this, textId("linkWithDocumentSelector") }; + PropertyBool forceOpenGlFallbackWidget{ this, textId("forceOpenGlFallbackWidget") }; // Meshing enum class BRepMeshQuality { VeryCoarse, Coarse, Normal, Precise, VeryPrecise, UserDefined }; PropertyEnum meshingQuality{ this, textId("meshingQuality") }; @@ -62,6 +63,7 @@ class AppModuleProperties : public PropertyGroup { PropertyEnum navigationStyle{ this, textId("navigationStyle") }; PropertyBool defaultShowOriginTrihedron{ this, textId("defaultShowOriginTrihedron") }; PropertyDouble instantZoomFactor{ this, textId("instantZoomFactor") }; + PropertyAngle turnViewAngleIncrement{ this, textId("turnViewAngleIncrement") }; // -- Graphics/ClipPlanes PropertyBool clipPlanesCappingOn{ this, textId("cappingOn") }; PropertyBool clipPlanesCappingHatchOn{ this, textId("cappingHatchOn") }; diff --git a/src/app/button_flat.h b/src/app/button_flat.h index a740a7c9..5e5ad350 100644 --- a/src/app/button_flat.h +++ b/src/app/button_flat.h @@ -6,7 +6,7 @@ #pragma once -#include "widgets_utils.h" +#include "qtwidgets_utils.h" #include #include diff --git a/src/app/cli_export.cpp b/src/app/cli_export.cpp index 3134baf4..f5d7d428 100644 --- a/src/app/cli_export.cpp +++ b/src/app/cli_export.cpp @@ -187,13 +187,13 @@ void cli_asyncExportDocuments( }; // Show progress/traces corresponding to task events - QObject::connect(taskMgr, &TaskManager::started, app, [=](TaskId taskId) { + taskMgr->signalStarted.connectSlot([=](TaskId taskId) { if (args.progressReport) fnPrintProgress(); else qInfo() << to_QString(taskMgr->title(taskId)); }); - QObject::connect(taskMgr, &TaskManager::ended, app, [=](TaskId taskId) { + taskMgr->signalEnded.connectSlot([=](TaskId taskId) { if (args.progressReport) { fnPrintProgress(); } @@ -204,13 +204,13 @@ void cli_asyncExportDocuments( qCritical() << to_QString(taskMgr->title(taskId)); } }); - QObject::connect(taskMgr, &TaskManager::progressChanged, app, [=]{ + taskMgr->signalProgressChanged.connectSlot([=]{ if (args.progressReport) fnPrintProgress(); }); helper->exportTaskCount = int(args.filesToExport.size()); - QObject::connect(taskMgr, &TaskManager::ended, app, [=]{ + taskMgr->signalEnded.connectSlot([=]{ if (helper->exportTaskCount == 0) { bool okExport = true; for (const auto& mapPair : helper->mapTaskStatus) { diff --git a/src/app/commands_api.cpp b/src/app/commands_api.cpp new file mode 100644 index 00000000..5ed5b40f --- /dev/null +++ b/src/app/commands_api.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 "commands_api.h" + +#include "../base/application.h" +#include "../gui/gui_application.h" + +#include + +namespace Mayo { + +IAppContext::IAppContext(QObject* parent) + : QObject(parent) +{ +} + +Command::Command(IAppContext* context) + : QObject(context ? context->widgetMain() : nullptr), + m_context(context) +{ +} + +Application* Command::app() const +{ + return m_context->guiApp()->application().get(); +} + +GuiDocument* Command::currentGuiDocument() const +{ + DocumentPtr doc = this->app()->findDocumentByIdentifier(this->currentDocument()); + return this->guiApp()->findGuiDocument(doc); +} + +int Command::currentDocumentIndex() const +{ + return this->context()->findDocumentIndex(this->currentDocument()); +} + +void Command::setCurrentDocument(const DocumentPtr& doc) +{ + m_context->setCurrentDocument(doc->identifier()); +} + +void Command::setAction(QAction* action) +{ + m_action = action; + QObject::connect(action, &QAction::triggered, this, &Command::execute); +} + +} // namespace Mayo diff --git a/src/app/commands_api.h b/src/app/commands_api.h new file mode 100644 index 00000000..755ce7e1 --- /dev/null +++ b/src/app/commands_api.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/document.h" +#include "../base/filepath.h" +#include "../base/text_id.h" + +#include +#include // WARNING Qt5 / Qt6 +class QWidget; + +namespace Mayo { + +class Application; +class GuiApplication; +class GuiDocument; +class V3dViewController; +class TaskManager; + +// Provides interface to access/interact with application +class IAppContext : public QObject { + Q_OBJECT +public: + enum class ModeWidgetMain { Unknown, Home, Documents }; + + IAppContext(QObject* parent = nullptr); + + virtual GuiApplication* guiApp() const = 0; + virtual TaskManager* taskMgr() const = 0; + virtual QWidget* widgetMain() const = 0; + virtual QWidget* widgetLeftSidebar() const = 0; + virtual ModeWidgetMain modeWidgetMain() const = 0; + virtual V3dViewController* v3dViewController(const GuiDocument* guiDoc) const = 0; + + virtual Document::Identifier currentDocument() const = 0; + virtual void setCurrentDocument(Document::Identifier docId) = 0; + + virtual int findDocumentIndex(Document::Identifier docId) const = 0; + virtual Document::Identifier findDocumentFromIndex(int index) const = 0; + + virtual void updateControlsEnabledStatus() = 0; + virtual void deleteDocumentWidget(const DocumentPtr& doc) = 0; + +signals: + void currentDocumentChanged(Mayo::Document::Identifier docId); +}; + +// Represents a single action in the application +class Command : public QObject { + Q_OBJECT + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::Command) +public: + Command(IAppContext* context); + virtual ~Command() = default; + + virtual void execute() = 0; + + IAppContext* context() const { return m_context; } + + QAction* action() const { return m_action; } + virtual bool getEnabledStatus() const { return true; } + +protected: + Application* app() const; + GuiApplication* guiApp() const { return m_context->guiApp(); } + TaskManager* taskMgr() const { return m_context->taskMgr(); } + QWidget* widgetMain() const { return m_context->widgetMain(); } + Document::Identifier currentDocument() const { return m_context->currentDocument(); } + GuiDocument* currentGuiDocument() const; + int currentDocumentIndex() const; + + void setCurrentDocument(const DocumentPtr& doc); + void setAction(QAction* action); + +private: + IAppContext* m_context = nullptr; + QAction* m_action = nullptr; +}; + +} // namespace Mayo diff --git a/src/app/commands_display.cpp b/src/app/commands_display.cpp new file mode 100644 index 00000000..1b021a6b --- /dev/null +++ b/src/app/commands_display.cpp @@ -0,0 +1,334 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "commands_display.h" + +#include "../base/application.h" +#include "../base/cpp_utils.h" +#include "../base/unit_system.h" +#include "../gui/gui_application.h" +#include "../gui/gui_document.h" +#include "../gui/v3d_view_controller.h" +#include "app_module.h" +#include "qstring_conv.h" +#include "theme.h" + +#include +#include +#include // WARNING Qt5 / Qt6 +#include + +namespace Mayo { + +CommandChangeProjection::CommandChangeProjection(IAppContext* context) + : Command(context) +{ + m_actionOrtho = new QAction(Command::tr("Orthographic"), this); + m_actionPersp = new QAction(Command::tr("Perspective"), this); + m_actionOrtho->setCheckable(true); + m_actionPersp->setCheckable(true); + + auto menu = new QMenu(this->widgetMain()); + menu->addAction(m_actionOrtho); + menu->addAction(m_actionPersp); + + auto group = new QActionGroup(menu); + group->setExclusive(true); + group->addAction(m_actionOrtho); + group->addAction(m_actionPersp); + + auto action = new QAction(this); + action->setText(Command::tr("Projection")); + action->setMenu(menu); + this->setAction(action); + + QObject::connect(group, &QActionGroup::triggered, this, [=](const QAction* action) { + GuiDocument* guiDoc = this->currentGuiDocument(); + if (guiDoc) { + guiDoc->v3dView()->Camera()->SetProjectionType( + action == m_actionOrtho ? + Graphic3d_Camera::Projection_Orthographic : + Graphic3d_Camera::Projection_Perspective + ); + guiDoc->graphicsView().redraw(); + } + }); + + QObject::connect( + context, &IAppContext::currentDocumentChanged, + this, &CommandChangeProjection::onCurrentDocumentChanged + ); +} + +void CommandChangeProjection::execute() +{ +} + +bool CommandChangeProjection::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +void CommandChangeProjection::onCurrentDocumentChanged() +{ + const GuiDocument* guiDoc = this->currentGuiDocument(); + if (!guiDoc) + return; + + // Sync menu with current projection type + const auto viewProjType = guiDoc->v3dView()->Camera()->ProjectionType(); + Q_ASSERT(viewProjType == Graphic3d_Camera::Projection_Perspective + || viewProjType == Graphic3d_Camera::Projection_Orthographic + ); + QAction* actionProj = viewProjType == Graphic3d_Camera::Projection_Perspective ? m_actionPersp : m_actionOrtho; + [[maybe_unused]] QSignalBlocker sigBlk(this->action()); + actionProj->setChecked(true); +} + +CommandChangeDisplayMode::CommandChangeDisplayMode(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Mode")); + this->setAction(action); +} + +CommandChangeDisplayMode::CommandChangeDisplayMode(IAppContext* context, QMenu* containerMenu) + : CommandChangeDisplayMode(context) +{ + QObject::connect( + containerMenu, &QMenu::aboutToShow, + this, &CommandChangeDisplayMode::recreateMenuDisplayMode + ); +} + +void CommandChangeDisplayMode::execute() +{ +} + +bool CommandChangeDisplayMode::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +void CommandChangeDisplayMode::recreateMenuDisplayMode() +{ + QMenu* menu = this->action()->menu(); + if (!menu) { + menu = new QMenu(this->widgetMain()); + this->action()->setMenu(menu); + } + + menu->clear(); + + GuiDocument* guiDoc = this->currentGuiDocument(); + if (!guiDoc) + return; + + const auto spanDrivers = this->guiApp()->graphicsObjectDrivers(); + for (const GraphicsObjectDriverPtr& driver : spanDrivers) { + if (driver->displayModes().empty()) + continue; // Skip + + if (driver != spanDrivers.front()) + menu->addSeparator(); + + auto group = new QActionGroup(menu); + group->setExclusive(true); + for (const Enumeration::Item& displayMode : driver->displayModes().items()) { + auto action = new QAction(to_QString(displayMode.name.tr()), menu); + action->setCheckable(true); + action->setData(displayMode.value); + menu->addAction(action); + group->addAction(action); + if (displayMode.value == guiDoc->activeDisplayMode(driver)) + action->setChecked(true); + } + + QObject::connect(group, &QActionGroup::triggered, this, [=](QAction* action) { + guiDoc->setActiveDisplayMode(driver, action->data().toInt()); + guiDoc->graphicsView().redraw(); + }); + } +} + +CommandToggleOriginTrihedron::CommandToggleOriginTrihedron(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Show Origin Trihedron")); + action->setToolTip(Command::tr("Show/Hide Origin Trihedron")); + action->setCheckable(true); + action->setChecked(false); + this->setAction(action); + + QObject::connect( + context, &IAppContext::currentDocumentChanged, + this, &CommandToggleOriginTrihedron::onCurrentDocumentChanged + ); +} + +void CommandToggleOriginTrihedron::execute() +{ + GuiDocument* guiDoc = this->currentGuiDocument(); + if (guiDoc) { + guiDoc->toggleOriginTrihedronVisibility(); + guiDoc->graphicsScene()->redraw(); + } +} + +bool CommandToggleOriginTrihedron::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +void CommandToggleOriginTrihedron::onCurrentDocumentChanged() +{ + GuiDocument* guiDoc = this->currentGuiDocument(); + if (guiDoc) { + // Sync action with current visibility status of origin trihedron + [[maybe_unused]] QSignalBlocker sigBlk(this->action()); + this->action()->setChecked(guiDoc->isOriginTrihedronVisible()); + } + else { + this->action()->setChecked(false); + } +} + +CommandTogglePerformanceStats::CommandTogglePerformanceStats(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Show Performance Stats")); + action->setToolTip(Command::tr("Show/Hide rendering performance statistics")); + action->setCheckable(true); + action->setChecked(false); + this->setAction(action); + + QObject::connect( + context, &IAppContext::currentDocumentChanged, + this, &CommandTogglePerformanceStats::onCurrentDocumentChanged + ); +} + +void CommandTogglePerformanceStats::execute() +{ + GuiDocument* guiDoc = this->currentGuiDocument(); + if (guiDoc) { + CppUtils::toggle(guiDoc->v3dView()->ChangeRenderingParams().ToShowStats); + guiDoc->graphicsView().redraw(); + } +} + +bool CommandTogglePerformanceStats::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +void CommandTogglePerformanceStats::onCurrentDocumentChanged() +{ + GuiDocument* guiDoc = this->currentGuiDocument(); + if (guiDoc) { + // Sync action with current visibility status of rendering performance stats + [[maybe_unused]] QSignalBlocker sigBlk(this->action()); + this->action()->setChecked(guiDoc->v3dView()->ChangeRenderingParams().ToShowStats); + } + else { + this->action()->setChecked(false); + } +} + +CommandZoomInCurrentDocument::CommandZoomInCurrentDocument(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Zoom In")); + action->setIcon(mayoTheme()->icon(Theme::Icon::ZoomIn)); + action->setShortcut(Qt::CTRL + Qt::Key_Plus); + this->setAction(action); +} + +void CommandZoomInCurrentDocument::execute() +{ + auto ctrl = this->context()->v3dViewController(this->currentGuiDocument()); + if (ctrl) + ctrl->zoomIn(); +} + +bool CommandZoomInCurrentDocument::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +CommandZoomOutCurrentDocument::CommandZoomOutCurrentDocument(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Zoom Out")); + action->setIcon(mayoTheme()->icon(Theme::Icon::ZoomOut)); + action->setShortcut(Qt::CTRL + Qt::Key_Minus); + this->setAction(action); +} + +void CommandZoomOutCurrentDocument::execute() +{ + auto ctrl = this->context()->v3dViewController(this->currentGuiDocument()); + if (ctrl) + ctrl->zoomOut(); +} + +bool CommandZoomOutCurrentDocument::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +CommandTurnViewCounterClockWise::CommandTurnViewCounterClockWise(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Turn Counter Clockwise")); + action->setIcon(mayoTheme()->icon(Theme::Icon::TurnCounterClockwise)); + action->setShortcut(Qt::CTRL + Qt::Key_Left); + this->setAction(action); +} + +void CommandTurnViewCounterClockWise::execute() +{ + const QuantityAngle increment = AppModule::get()->properties()->turnViewAngleIncrement.quantity(); + auto ctrl = this->context()->v3dViewController(this->currentGuiDocument()); + if (ctrl) + ctrl->turn(V3d_Z, -increment); +} + +bool CommandTurnViewCounterClockWise::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +CommandTurnViewClockWise::CommandTurnViewClockWise(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Turn Clockwise")); + action->setIcon(mayoTheme()->icon(Theme::Icon::TurnClockwise)); + action->setShortcut(Qt::CTRL + Qt::Key_Right); + this->setAction(action); +} + +void CommandTurnViewClockWise::execute() +{ + const QuantityAngle increment = AppModule::get()->properties()->turnViewAngleIncrement.quantity(); + auto ctrl = this->context()->v3dViewController(this->currentGuiDocument()); + if (ctrl) + ctrl->turn(V3d_Z, increment); +} + +bool CommandTurnViewClockWise::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +} // namespace Mayo diff --git a/src/app/commands_display.h b/src/app/commands_display.h new file mode 100644 index 00000000..14dc6d50 --- /dev/null +++ b/src/app/commands_display.h @@ -0,0 +1,86 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "commands_api.h" + +class QMenu; + +namespace Mayo { + +class CommandChangeProjection : public Command { +public: + CommandChangeProjection(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; + +private: + void onCurrentDocumentChanged(); + QAction* m_actionOrtho = nullptr; + QAction* m_actionPersp = nullptr; +}; + +class CommandChangeDisplayMode : public Command { +public: + CommandChangeDisplayMode(IAppContext* context); + CommandChangeDisplayMode(IAppContext* context, QMenu* containerMenu); + void execute() override; + bool getEnabledStatus() const override; + +private: + void recreateMenuDisplayMode(); +}; + +class CommandToggleOriginTrihedron : public Command { +public: + CommandToggleOriginTrihedron(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; + +private: + void onCurrentDocumentChanged(); +}; + +class CommandTogglePerformanceStats : public Command { +public: + CommandTogglePerformanceStats(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; + +private: + void onCurrentDocumentChanged(); +}; + +class CommandZoomInCurrentDocument : public Command { +public: + CommandZoomInCurrentDocument(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandZoomOutCurrentDocument : public Command { +public: + CommandZoomOutCurrentDocument(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandTurnViewCounterClockWise : public Command { +public: + CommandTurnViewCounterClockWise(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandTurnViewClockWise : public Command { +public: + CommandTurnViewClockWise(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +} // namespace Mayo diff --git a/src/app/commands_file.cpp b/src/app/commands_file.cpp new file mode 100644 index 00000000..133d4d82 --- /dev/null +++ b/src/app/commands_file.cpp @@ -0,0 +1,577 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "commands_file.h" + +#include "../base/application.h" +#include "../base/task_manager.h" +#include "../gui/gui_application.h" +#include "app_module.h" +#include "filepath_conv.h" +#include "qstring_conv.h" +#include "recent_files.h" +#include "theme.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Mayo { + +namespace { + +QString fileFilter(IO::Format format) +{ + if (format == IO::Format_Unknown) + return {}; + + QString filter; + for (std::string_view suffix : IO::formatFileSuffixes(format)) { + if (suffix.data() != IO::formatFileSuffixes(format).front().data()) + filter += " "; + + const QString qsuffix = to_QString(suffix); + filter += "*." + qsuffix; +#ifdef Q_OS_UNIX + filter += " *." + qsuffix.toUpper(); +#endif + } + + //: %1 is the format identifier and %2 is the file filters string + return Command::tr("%1 files(%2)") + .arg(to_QString(IO::formatIdentifier(format))) + .arg(filter); +} + +IO::Format formatFromFilter(const QString& filter) +{ + for (IO::Format format : AppModule::get()->ioSystem()->readerFormats()) { + if (filter == fileFilter(format)) + return format; + } + + for (IO::Format format : AppModule::get()->ioSystem()->writerFormats()) { + if (filter == fileFilter(format)) + return format; + } + + return IO::Format_Unknown; +} + +// TODO: move in Options +struct ImportExportSettings { + FilePath openDir; + QString selectedFilter; + + static ImportExportSettings load() + { + return { + AppModule::get()->properties()->lastOpenDir.value(), + to_QString(AppModule::get()->properties()->lastSelectedFormatFilter.value()) + }; + } + + static void save(const ImportExportSettings& sets) + { + AppModule::get()->properties()->lastOpenDir.setValue(sets.openDir); + AppModule::get()->properties()->lastSelectedFormatFilter.setValue(to_stdString(sets.selectedFilter)); + } +}; + +struct OpenFileNames { + std::vector listFilepath; + ImportExportSettings lastIoSettings; + IO::Format selectedFormat; + + enum GetOption { + GetOne, + GetMany + }; + + static OpenFileNames get( + QWidget* parentWidget, + OpenFileNames::GetOption option = OpenFileNames::GetMany) + { + OpenFileNames result; + result.selectedFormat = IO::Format_Unknown; + result.lastIoSettings = ImportExportSettings::load(); + QStringList listFormatFilter; + for (IO::Format format : AppModule::get()->ioSystem()->readerFormats()) + listFormatFilter += fileFilter(format); + + const QString allFilesFilter = Command::tr("All files(*.*)"); + listFormatFilter.append(allFilesFilter); + const QString dlgTitle = Command::tr("Select Part File"); + const QString dlgOpenDir = filepathTo(result.lastIoSettings.openDir); + const QString dlgFilter = listFormatFilter.join(QLatin1String(";;")); + QString* dlgPtrSelFilter = &result.lastIoSettings.selectedFilter; + if (option == OpenFileNames::GetOne) { + const QString strFilepath = + QFileDialog::getOpenFileName( + parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); + result.listFilepath.clear(); + result.listFilepath.push_back(filepathFrom(strFilepath)); + } + else { + const QStringList listStrFilePath = + QFileDialog::getOpenFileNames( + parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); + result.listFilepath.clear(); + for (const QString& strFilePath : listStrFilePath) + result.listFilepath.push_back(filepathFrom(strFilePath)); + } + + if (!result.listFilepath.empty()) { + result.lastIoSettings.openDir = result.listFilepath.front(); + result.selectedFormat = + result.lastIoSettings.selectedFilter != allFilesFilter ? + formatFromFilter(result.lastIoSettings.selectedFilter) : + IO::Format_Unknown; + ImportExportSettings::save(result.lastIoSettings); + } + + return result; + } +}; + +QString strFilepathQuoted(const QString& filepath) +{ + for (QChar c : filepath) { + if (c.isSpace()) + return "\"" + filepath + "\""; + } + + return filepath; +} + +} // namespace + + +void FileCommandTools::closeDocument(IAppContext* context, Document::Identifier docId) +{ + auto app = context->guiApp()->application(); + DocumentPtr doc = app->findDocumentByIdentifier(docId); + context->deleteDocumentWidget(doc); + app->closeDocument(doc); + context->updateControlsEnabledStatus(); +} + +void FileCommandTools::openDocumentsFromList(IAppContext* context, Span listFilePath) +{ + auto app = context->guiApp()->application(); + auto appModule = AppModule::get(); + for (const FilePath& fp : listFilePath) { + DocumentPtr docPtr = app->findDocumentByLocation(fp); + if (docPtr.IsNull()) { + docPtr = app->newDocument(); + docPtr->setName(fp.filename().u8string()); + docPtr->setFilePath(fp); + // Use the Document identifier instead of handle within the job function(capture) + // Using the handle increases the ref count and the task will be released on next + // task creation, so the document won't be destroyed + const Document::Identifier newDocId = docPtr->identifier(); + const TaskId taskId = context->taskMgr()->newTask([=](TaskProgress* progress) { + QElapsedTimer chrono; + chrono.start(); + const bool okImport = + appModule->ioSystem()->importInDocument() + .targetDocument(app->findDocumentByIdentifier(newDocId)) + .withFilepath(fp) + .withParametersProvider(appModule) + .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { + appModule->computeBRepMesh(labelEntity, progress); + }) + .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) + .withEntityPostProcessInfoProgress(20, Command::textIdTr("Mesh BRep shapes")) + .withMessenger(appModule) + .withTaskProgress(progress) + .execute(); + if (okImport) + appModule->emitInfo(fmt::format(Command::textIdTr("Import time: {}ms"), chrono.elapsed())); + }); + context->taskMgr()->setTitle(taskId, fp.stem().u8string()); + context->taskMgr()->run(taskId); + appModule->prependRecentFile(fp); + } + else { + if (listFilePath.size() == 1) + context->setCurrentDocument(docPtr->identifier()); + } + } // endfor() +} + +void FileCommandTools::openDocument(IAppContext* context, FilePath fp) +{ + FileCommandTools::openDocumentsFromList(context, Span(&fp, 1)); +} + +CommandNewDocument::CommandNewDocument(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("New")); + action->setToolTip(Command::tr("New Document")); + action->setShortcut(Qt::CTRL + Qt::Key_N); + this->setAction(action); +} + +void CommandNewDocument::execute() +{ + static unsigned docSequenceId = 0; + auto docPtr = this->app()->newDocument(Document::Format::Binary); + docPtr->setName(to_stdString(Command::tr("Anonymous%1").arg(++docSequenceId))); +} + +CommandOpenDocuments::CommandOpenDocuments(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Open")); + action->setToolTip(Command::tr("Open Documents")); + action->setShortcut(Qt::CTRL + Qt::Key_O); + this->setAction(action); + + context->widgetMain()->setAcceptDrops(true); + context->widgetMain()->installEventFilter(this); +} + +void CommandOpenDocuments::execute() +{ + const auto resFileNames = OpenFileNames::get(this->widgetMain()); + if (!resFileNames.listFilepath.empty()) + FileCommandTools::openDocumentsFromList(this->context(), resFileNames.listFilepath); +} + +bool CommandOpenDocuments::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == this->widgetMain()) { + if (event->type() == QEvent::DragEnter) { + auto dragEnterEvent = static_cast(event); + if (dragEnterEvent->mimeData()->hasUrls()) + dragEnterEvent->acceptProposedAction(); + + return true; + } + else if (event->type() == QEvent::Drop) { + auto dropEvent = static_cast(event); + const QList listUrl = dropEvent->mimeData()->urls(); + std::vector listFilePath; + for (const QUrl& url : listUrl) { + if (url.isLocalFile()) + listFilePath.push_back(filepathFrom(url.toLocalFile())); + } + + dropEvent->acceptProposedAction(); + FileCommandTools::openDocumentsFromList(this->context(), listFilePath); + return true; + } + else { + return false; + } + } + + return Command::eventFilter(watched, event); +} + +CommandRecentFiles::CommandRecentFiles(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Recent files")); + this->setAction(action); +} + +CommandRecentFiles::CommandRecentFiles(IAppContext* context, QMenu* containerMenu) + : CommandRecentFiles(context) +{ + QObject::connect( + containerMenu, &QMenu::aboutToShow, + this, &CommandRecentFiles::recreateEntries + ); +} + +void CommandRecentFiles::execute() +{ +} + +void CommandRecentFiles::recreateEntries() +{ + QMenu* menu = this->action()->menu(); + if (!menu) + menu = new QMenu(this->widgetMain()); + + menu->clear(); + int idFile = 0; + auto appModule = AppModule::get(); + const RecentFiles& recentFiles = appModule->properties()->recentFiles.value(); + for (const RecentFile& recentFile : recentFiles) { + const QString strFilePath = filepathTo(recentFile.filepath); + const QString strEntryRecentFile = Command::tr("%1 | %2").arg(++idFile).arg(strFilePath); + menu->addAction(strEntryRecentFile, this, [=]{ + FileCommandTools::openDocument(this->context(), recentFile.filepath); + }); + } + + if (!recentFiles.empty()) { + menu->addSeparator(); + menu->addAction(Command::tr("Clear menu"), this, [=]{ + menu->clear(); + appModule->properties()->recentFiles.setValue({}); + }); + } + + this->action()->setMenu(menu); +} + +CommandImportInCurrentDocument::CommandImportInCurrentDocument(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Import")); + action->setToolTip(Command::tr("Import in current document")); + action->setIcon(mayoTheme()->icon(Theme::Icon::Import)); + this->setAction(action); +} + +void CommandImportInCurrentDocument::execute() +{ + const GuiDocument* guiDoc = this->currentGuiDocument(); + if (!guiDoc) + return; + + const auto resFileNames = OpenFileNames::get(this->widgetMain()); + if (resFileNames.listFilepath.empty()) + return; + + auto appModule = AppModule::get(); + const TaskId taskId = this->taskMgr()->newTask([=](TaskProgress* progress) { + QElapsedTimer chrono; + chrono.start(); + + const bool okImport = appModule->ioSystem()->importInDocument() + .targetDocument(guiDoc->document()) + .withFilepaths(resFileNames.listFilepath) + .withParametersProvider(appModule) + .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { + appModule->computeBRepMesh(labelEntity, progress); + }) + .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) + .withEntityPostProcessInfoProgress(20, Command::textIdTr("Mesh BRep shapes")) + .withMessenger(appModule) + .withTaskProgress(progress) + .execute(); + if (okImport) + appModule->emitInfo(fmt::format(Command::textIdTr("Import time: {}ms"), chrono.elapsed())); + }); + const QString taskTitle = + resFileNames.listFilepath.size() > 1 ? + Command::tr("Import") : + filepathTo(resFileNames.listFilepath.front().stem()); + this->taskMgr()->setTitle(taskId, to_stdString(taskTitle)); + this->taskMgr()->run(taskId); + for (const FilePath& fp : resFileNames.listFilepath) + appModule->prependRecentFile(fp); +} + +bool CommandImportInCurrentDocument::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +CommandExportSelectedApplicationItems::CommandExportSelectedApplicationItems(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Export selected items")); + action->setToolTip(Command::tr("Export selected items")); + action->setIcon(mayoTheme()->icon(Theme::Icon::Export)); + this->setAction(action); +} + +void CommandExportSelectedApplicationItems::execute() +{ + auto appModule = AppModule::get(); + if (this->guiApp()->selectionModel()->selectedItems().empty()) { + appModule->emitError(Command::textIdTr("No item selected for export")); + return; + } + + QStringList listWriterFileFilter; + for (IO::Format format : appModule->ioSystem()->writerFormats()) + listWriterFileFilter.append(fileFilter(format)); + + auto lastSettings = ImportExportSettings::load(); + const QString strFilepath = + QFileDialog::getSaveFileName( + this->widgetMain(), + Command::tr("Select Output File"), + filepathTo(lastSettings.openDir), + listWriterFileFilter.join(QLatin1String(";;")), + &lastSettings.selectedFilter); + if (strFilepath.isEmpty()) + return; + + lastSettings.openDir = filepathFrom(strFilepath); + const IO::Format format = formatFromFilter(lastSettings.selectedFilter); + const TaskId taskId = this->taskMgr()->newTask([=](TaskProgress* progress) { + QElapsedTimer chrono; + chrono.start(); + const bool okExport = + appModule->ioSystem()->exportApplicationItems() + .targetFile(filepathFrom(strFilepath)) + .targetFormat(format) + .withItems(this->guiApp()->selectionModel()->selectedItems()) + .withParameters(appModule->findWriterParameters(format)) + .withMessenger(appModule) + .withTaskProgress(progress) + .execute(); + if (okExport) + appModule->emitInfo(fmt::format(Command::textIdTr("Export time: {}ms"), chrono.elapsed())); + }); + this->taskMgr()->setTitle(taskId, to_stdString(QFileInfo(strFilepath).fileName())); + this->taskMgr()->run(taskId); + ImportExportSettings::save(lastSettings); +} + +bool CommandExportSelectedApplicationItems::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +CommandCloseCurrentDocument::CommandCloseCurrentDocument(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Close \"%1\"")); + action->setToolTip(action->text()); + action->setIcon(mayoTheme()->icon(Theme::Icon::Cross)); + action->setShortcut(Qt::CTRL + Qt::Key_W); + this->setAction(action); + + QObject::connect( + context, &IAppContext::currentDocumentChanged, + this, &CommandCloseCurrentDocument::updateActionText + ); + this->app()->signalDocumentNameChanged.connectSlot([=](const DocumentPtr& doc) { + if (this->currentDocument() == doc->identifier()) + this->updateActionText(this->currentDocument()); + }); + + this->updateActionText(-1); +} + +void CommandCloseCurrentDocument::execute() +{ + FileCommandTools::closeDocument(this->context(), this->currentDocument()); +} + +bool CommandCloseCurrentDocument::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +void CommandCloseCurrentDocument::updateActionText(Document::Identifier docId) +{ + DocumentPtr docPtr = this->app()->findDocumentByIdentifier(docId); + const QString docName = to_QString(docPtr ? docPtr->name() : std::string{}); + const QString textActionClose = + docPtr ? + Command::tr("Close \"%1\"").arg(strFilepathQuoted(docName)) : + Command::tr("Close"); + this->action()->setText(textActionClose); + this->action()->setToolTip(textActionClose); +} + +CommandCloseAllDocuments::CommandCloseAllDocuments(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Close all")); + action->setToolTip(Command::tr("Close all documents")); + action->setShortcut((Qt::CTRL | Qt::SHIFT) + Qt::Key_W); + this->setAction(action); +} + +void CommandCloseAllDocuments::execute() +{ + while (!this->guiApp()->guiDocuments().empty()) + FileCommandTools::closeDocument(this->context(), this->currentDocument()); +} + +bool CommandCloseAllDocuments::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +CommandCloseAllDocumentsExceptCurrent::CommandCloseAllDocumentsExceptCurrent(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Close all except current")); + action->setToolTip(Command::tr("Close all except current document")); + this->setAction(action); + + QObject::connect( + context, &IAppContext::currentDocumentChanged, + this, &CommandCloseAllDocumentsExceptCurrent::updateActionText + ); + this->app()->signalDocumentNameChanged.connectSlot([=](const DocumentPtr& doc) { + if (this->currentDocument() == doc->identifier()) + this->updateActionText(this->currentDocument()); + }); + + this->updateActionText(-1); +} + +void CommandCloseAllDocumentsExceptCurrent::execute() +{ + GuiDocument* currentGuiDoc = this->currentGuiDocument(); + std::vector vecGuiDoc; + for (GuiDocument* guiDoc : this->guiApp()->guiDocuments()) + vecGuiDoc.push_back(guiDoc); + + for (GuiDocument* guiDoc : vecGuiDoc) { + if (guiDoc != currentGuiDoc) + FileCommandTools::closeDocument(this->context(), guiDoc->document()->identifier()); + } +} + +bool CommandCloseAllDocumentsExceptCurrent::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +void CommandCloseAllDocumentsExceptCurrent::updateActionText(Document::Identifier docId) +{ + DocumentPtr docPtr = this->app()->findDocumentByIdentifier(docId); + const QString docName = to_QString(docPtr ? docPtr->name() : std::string{}); + const QString textActionClose = + docPtr ? + Command::tr("Close all except \"%1\"").arg(strFilepathQuoted(docName)) : + Command::tr("Close all except current"); + this->action()->setText(textActionClose); +} + +CommandQuitApplication::CommandQuitApplication(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Quit")); + this->setAction(action); +} + +void CommandQuitApplication::execute() +{ + QApplication::quit(); +} + +} // namespace Mayo diff --git a/src/app/commands_file.h b/src/app/commands_file.h new file mode 100644 index 00000000..bc06dcf7 --- /dev/null +++ b/src/app/commands_file.h @@ -0,0 +1,90 @@ +/**************************************************************************** +** 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 "commands_api.h" + +namespace Mayo { + +class FileCommandTools { +public: + static void closeDocument(IAppContext* context, Document::Identifier docId); + static void openDocumentsFromList(IAppContext* context, Span listFilePath); + static void openDocument(IAppContext* context, FilePath fp); +}; + +class CommandNewDocument : public Command { +public: + CommandNewDocument(IAppContext* context); + void execute() override; +}; + +class CommandOpenDocuments : public Command { +public: + CommandOpenDocuments(IAppContext* context); + void execute() override; + bool eventFilter(QObject* watched, QEvent* event) override; +}; + +class CommandRecentFiles : public Command { +public: + CommandRecentFiles(IAppContext* context); + CommandRecentFiles(IAppContext* context, QMenu* containerMenu); + void execute() override; + void recreateEntries(); +}; + +class CommandImportInCurrentDocument : public Command { +public: + CommandImportInCurrentDocument(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandExportSelectedApplicationItems : public Command { +public: + CommandExportSelectedApplicationItems(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandCloseCurrentDocument : public Command { +public: + CommandCloseCurrentDocument(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; + +private: + void updateActionText(Document::Identifier docId); +}; + +class CommandCloseAllDocuments : public Command { +public: + CommandCloseAllDocuments(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandCloseAllDocumentsExceptCurrent : public Command { +public: + CommandCloseAllDocumentsExceptCurrent(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; + +private: + void updateActionText(Document::Identifier docId); +}; + +class CommandQuitApplication : public Command { +public: + CommandQuitApplication(IAppContext* context); + void execute() override; +}; + +} // namespace Mayo diff --git a/src/app/commands_help.cpp b/src/app/commands_help.cpp new file mode 100644 index 00000000..b7fe8f3f --- /dev/null +++ b/src/app/commands_help.cpp @@ -0,0 +1,45 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "commands_help.h" + +#include "dialog_about.h" +#include "qtwidgets_utils.h" + +#include +#include +#include + +namespace Mayo { + +CommandReportBug::CommandReportBug(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Report Bug")); + this->setAction(action); +} + +void CommandReportBug::execute() +{ + QDesktopServices::openUrl(QUrl(QStringLiteral("https://github.com/fougue/mayo/issues"))); +} + +CommandAbout::CommandAbout(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("About %1").arg(QApplication::applicationName())); + this->setAction(action); +} + +void CommandAbout::execute() +{ + auto dlg = new DialogAbout(this->widgetMain()); + QtWidgetsUtils::asyncDialogExec(dlg); +} + +} // namespace Mayo diff --git a/src/app/commands_help.h b/src/app/commands_help.h new file mode 100644 index 00000000..ca880669 --- /dev/null +++ b/src/app/commands_help.h @@ -0,0 +1,25 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "commands_api.h" + +namespace Mayo { + +class CommandReportBug : public Command { +public: + CommandReportBug(IAppContext* context); + void execute() override; +}; + +class CommandAbout : public Command { +public: + CommandAbout(IAppContext* context); + void execute() override; +}; + +} // namespace Mayo diff --git a/src/app/commands_tools.cpp b/src/app/commands_tools.cpp new file mode 100644 index 00000000..a8d52bb9 --- /dev/null +++ b/src/app/commands_tools.cpp @@ -0,0 +1,99 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "commands_tools.h" + +#include "../base/application.h" +#include "../base/application_item_selection_model.h" +#include "../gui/gui_application.h" +#include "../gui/gui_document.h" +#include "app_module.h" +#include "dialog_inspect_xde.h" +#include "dialog_options.h" +#include "dialog_save_image_view.h" +#include "qtwidgets_utils.h" +#include "theme.h" + +#include + +namespace Mayo { + +CommandSaveViewImage::CommandSaveViewImage(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Save View to Image")); + action->setToolTip(Command::tr("Save View to Image")); + action->setIcon(mayoTheme()->icon(Theme::Icon::Camera)); + this->setAction(action); +} + +void CommandSaveViewImage::execute() +{ + auto guiDoc = this->currentGuiDocument(); + auto dlg = new DialogSaveImageView(guiDoc->v3dView(), this->widgetMain()); + QtWidgetsUtils::asyncDialogExec(dlg); +} + +bool CommandSaveViewImage::getEnabledStatus() const +{ + return this->app()->documentCount() != 0; +} + +CommandInspectXde::CommandInspectXde(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Inspect XDE")); + action->setToolTip(Command::tr("Inspect XDE")); + this->setAction(action); +} + +void CommandInspectXde::execute() +{ + const Span spanAppItem = this->guiApp()->selectionModel()->selectedItems(); + DocumentPtr doc; + for (const ApplicationItem& appItem : spanAppItem) { + if (appItem.document()->isXCafDocument()) { + doc = appItem.document(); + break; + } + } + + if (doc) { + auto dlg = new DialogInspectXde(this->widgetMain()); + dlg->load(doc); + QtWidgetsUtils::asyncDialogExec(dlg); + } +} + +bool CommandInspectXde::getEnabledStatus() const +{ + Span spanSelectedAppItem = this->guiApp()->selectionModel()->selectedItems(); + const ApplicationItem firstAppItem = + !spanSelectedAppItem.empty() ? spanSelectedAppItem.front() : ApplicationItem(); + return spanSelectedAppItem.size() == 1 + && firstAppItem.isValid() + && firstAppItem.document()->isXCafDocument(); +} + +CommandEditOptions::CommandEditOptions(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Options")); + action->setToolTip(Command::tr("Options")); + this->setAction(action); +} + +void CommandEditOptions::execute() +{ + auto dlg = new DialogOptions(AppModule::get()->settings(), this->widgetMain()); + QtWidgetsUtils::asyncDialogExec(dlg); +} + +} // namespace Mayo { + diff --git a/src/app/commands_tools.h b/src/app/commands_tools.h new file mode 100644 index 00000000..5e3e803e --- /dev/null +++ b/src/app/commands_tools.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 "commands_api.h" + +namespace Mayo { + +class CommandSaveViewImage : public Command { +public: + CommandSaveViewImage(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandInspectXde : public Command { +public: + CommandInspectXde(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandEditOptions : public Command { +public: + CommandEditOptions(IAppContext* context); + void execute() override; +}; + +} // namespace Mayo diff --git a/src/app/commands_window.cpp b/src/app/commands_window.cpp new file mode 100644 index 00000000..971497d2 --- /dev/null +++ b/src/app/commands_window.cpp @@ -0,0 +1,142 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "commands_window.h" + +#include "../base/application.h" +#include "theme.h" + +#include +#include + +namespace Mayo { + +CommandMainWidgetToggleFullscreen::CommandMainWidgetToggleFullscreen(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Fullscreen")); + action->setToolTip(Command::tr("Switch Fullscreen/Normal")); + action->setShortcut(Qt::Key_F11); + action->setCheckable(true); + action->setChecked(context->widgetMain()->isFullScreen()); + this->setAction(action); +} + +void CommandMainWidgetToggleFullscreen::execute() +{ + auto widget = this->widgetMain(); + if (widget->isFullScreen()) { + if (m_previousWindowState.testFlag(Qt::WindowMaximized)) + widget->showMaximized(); + else + widget->showNormal(); + } + else { + m_previousWindowState = widget->windowState(); + widget->showFullScreen(); + } +} + +CommandLeftSidebarWidgetToggle::CommandLeftSidebarWidgetToggle(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setToolTip(Command::tr("Show/Hide Left Sidebar")); + action->setShortcut(Qt::ALT + Qt::Key_0); + action->setCheckable(true); + action->setChecked(context->widgetLeftSidebar()->isVisible()); + this->setAction(action); + this->updateAction(); + context->widgetLeftSidebar()->installEventFilter(this); +} + +void CommandLeftSidebarWidgetToggle::execute() +{ + const bool isVisible = this->context()->widgetLeftSidebar()->isVisible(); + this->context()->widgetLeftSidebar()->setVisible(!isVisible); +} + +bool CommandLeftSidebarWidgetToggle::getEnabledStatus() const +{ + return this->context()->modeWidgetMain() != IAppContext::ModeWidgetMain::Home; +} + +bool CommandLeftSidebarWidgetToggle::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == this->context()->widgetLeftSidebar()) { + if (event->type() == QEvent::Show || event->type() == QEvent::Hide) { + this->updateAction(); + return true; + } + else { + return false; + } + } + + return Command::eventFilter(watched, event); +} + +void CommandLeftSidebarWidgetToggle::updateAction() +{ + if (this->context()->widgetLeftSidebar()->isVisible()) { + this->action()->setText(Command::tr("Hide Left Sidebar")); + this->action()->setIcon(mayoTheme()->icon(Theme::Icon::BackSquare)); + } + else { + this->action()->setText(Command::tr("Show Left Sidebar")); + this->action()->setIcon(mayoTheme()->icon(Theme::Icon::LeftSidebar)); + } + + this->action()->setToolTip(this->action()->text()); +} + +CommandPreviousDocument::CommandPreviousDocument(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Previous Document")); + action->setToolTip(Command::tr("Previous Document")); + action->setIcon(mayoTheme()->icon(Theme::Icon::Back)); + action->setShortcut(Qt::ALT + Qt::Key_Left); + this->setAction(action); +} + +void CommandPreviousDocument::execute() +{ + const int prevDocIndex = this->currentDocumentIndex() - 1; + this->context()->setCurrentDocument(this->context()->findDocumentFromIndex(prevDocIndex)); +} + +bool CommandPreviousDocument::getEnabledStatus() const +{ + return this->app()->documentCount() != 0 && this->currentDocumentIndex() > 0; +} + +CommandNextDocument::CommandNextDocument(IAppContext* context) + : Command(context) +{ + auto action = new QAction(this); + action->setText(Command::tr("Next Document")); + action->setToolTip(Command::tr("Next Document")); + action->setIcon(mayoTheme()->icon(Theme::Icon::Next)); + action->setShortcut(Qt::ALT + Qt::Key_Right); + this->setAction(action); +} + +void CommandNextDocument::execute() +{ + const int nextDocIndex = this->currentDocumentIndex() + 1; + this->context()->setCurrentDocument(this->context()->findDocumentFromIndex(nextDocIndex)); +} + +bool CommandNextDocument::getEnabledStatus() const +{ + const int appDocumentCount = this->app()->documentCount(); + return appDocumentCount != 0 && this->currentDocumentIndex() < appDocumentCount - 1; +} + +} // namespace Mayo diff --git a/src/app/commands_window.h b/src/app/commands_window.h new file mode 100644 index 00000000..008ffbc8 --- /dev/null +++ b/src/app/commands_window.h @@ -0,0 +1,48 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "commands_api.h" + +namespace Mayo { + +class CommandMainWidgetToggleFullscreen : public Command { +public: + CommandMainWidgetToggleFullscreen(IAppContext* context); + void execute() override; + +private: + Qt::WindowStates m_previousWindowState = Qt::WindowNoState; +}; + +class CommandLeftSidebarWidgetToggle : public Command { +public: + CommandLeftSidebarWidgetToggle(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; + + bool eventFilter(QObject* watched, QEvent* event) override; + +private: + void updateAction(); +}; + +class CommandPreviousDocument : public Command { +public: + CommandPreviousDocument(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +class CommandNextDocument : public Command { +public: + CommandNextDocument(IAppContext* context); + void execute() override; + bool getEnabledStatus() const override; +}; + +} // namespace Mayo diff --git a/src/app/dialog_inspect_xde.cpp b/src/app/dialog_inspect_xde.cpp index 015aa886..90ea81b6 100644 --- a/src/app/dialog_inspect_xde.cpp +++ b/src/app/dialog_inspect_xde.cpp @@ -12,12 +12,12 @@ #include "../base/meta_enum.h" #include "../base/settings.h" #include "../base/tkernel_utils.h" -#include "../gui/qtgui_utils.h" #include "app_module.h" #include "qmeta_tdf_label.h" #include "qstring_conv.h" #include "qstring_utils.h" -#include "widgets_utils.h" +#include "qtgui_utils.h" +#include "qtwidgets_utils.h" #include "ui_dialog_inspect_xde.h" #include @@ -55,7 +55,12 @@ # include #endif +#include +#include + +#include #include +#include namespace Mayo { @@ -283,14 +288,73 @@ static void loadLabelMaterialProperties( } #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) -static QTreeWidgetItem* createPropertyTreeItem( - const QString& text, const opencascade::handle& imgTexture) + +// Provides a QTreeWidgetItem specialized to display an image file with a tooltip +// QTreeWidgetItem::setToolTip() could be used but it forces all image files to be loaded on +// tree item construction +// This helper allows "lazy" loading of the image files +class ImageFileTreeWidgetItem : public QTreeWidgetItem { +public: + void setImageFilePath(int col, const QString& strFilePath) + { + const ItemData item{strFilePath, {}}; + m_mapColumnItemData.insert({ col, item }); + } + + QVariant data(int column, int role) const override + { + if (role != Qt::ToolTipRole) + return QTreeWidgetItem::data(column, role); + + auto itItem = m_mapColumnItemData.find(column); + ItemData* ptrItem = itItem != m_mapColumnItemData.end() ? &itItem->second : nullptr; + if (!ptrItem) + return {}; + + if (ptrItem->strToolTip.isEmpty()) { + const QPixmap pixmap(ptrItem->strFilePath); + if (!pixmap.isNull()) { + QBuffer bufferPixmap; + const QPixmap pixmapClamped = pixmap.scaledToWidth(std::min(pixmap.width(), 400)); + pixmapClamped.save(&bufferPixmap, "PNG"); + const auto imageSize = QFileInfo(ptrItem->strFilePath).size(); + const QString strImageSize = QStringUtils::bytesText(imageSize, appDefaultTextOptions().locale); + ptrItem->strToolTip = + QString("

%4

") + .arg(QString::fromLatin1(bufferPixmap.data().toBase64())) + .arg(pixmapClamped.width()) + .arg(pixmapClamped.height()) + .arg(DialogInspectXde::tr("File Size: %1
Dimensions: %2x%3 Depth: %4") + .arg(strImageSize).arg(pixmap.width()).arg(pixmap.height()).arg(pixmap.depth())) + ; + } + else { + ptrItem->strToolTip = DialogInspectXde::tr("Error when loading texture file(invalid path?)"); + } + } + + return ptrItem->strToolTip; + } + +private: + struct ItemData { + QString strFilePath; + QString strToolTip; + }; + + mutable std::unordered_map m_mapColumnItemData; +}; + +static QTreeWidgetItem* createPropertyTreeItem(const QString& text, const Handle(Image_Texture)& imgTexture) { if (imgTexture.IsNull()) return static_cast(nullptr); - auto item = createPropertyTreeItem(text); - item->setText(1, to_QString(imgTexture->FilePath())); + const QString strTextureFilePath = to_QString(imgTexture->FilePath()); + auto item = new ImageFileTreeWidgetItem; + item->setText(0, text); + item->setText(1, strTextureFilePath); + item->setImageFilePath(1, strTextureFilePath); return item; } #endif @@ -655,7 +719,7 @@ void DialogInspectXde::load(const Handle_TDocStd_Document& doc) { m_doc = doc; if (!XCAFDoc_DocumentTool::IsXCAFDocument(doc)) { - WidgetsUtils::asyncMsgBoxCritical( + QtWidgetsUtils::asyncMsgBoxCritical( this, tr("Error"), tr("This document is not suitable for XDE")); return; } diff --git a/src/app/dialog_options.cpp b/src/app/dialog_options.cpp index 587d4e04..da01a316 100644 --- a/src/app/dialog_options.cpp +++ b/src/app/dialog_options.cpp @@ -10,13 +10,13 @@ #include "../base/settings.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" -#include "../gui/qtgui_utils.h" #include "app_module.h" #include "item_view_buttons.h" #include "qsettings_storage.h" #include "qstring_conv.h" +#include "qtgui_utils.h" +#include "qtwidgets_utils.h" #include "theme.h" -#include "widgets_utils.h" #include "ui_dialog_options.h" #include @@ -147,7 +147,8 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) treeViewBtns->addButton( idBtnRestore, mayoTheme()->icon(Theme::Icon::Reload), - tr("Restore default values")); + tr("Restore default values") + ); treeViewBtns->setButtonDetection(idBtnRestore, -1, {}); treeViewBtns->setButtonDisplayColumn(idBtnRestore, 0); treeViewBtns->setButtonDisplayModes(idBtnRestore, ItemViewButtons::DisplayWhenItemSelected); @@ -204,15 +205,14 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) } // Enable/disable editor widget when the corresponding setting status is changed - QObject::connect(settings, &Settings::enabled, this, [=](const Property* setting, bool on) { + settings->signalEnabled.connectSlot([=](const Property* setting, bool on) { QWidget* editor = CppUtils::findValue(setting, m_mapSettingEditor); if (editor) editor->setEnabled(on); }); // Backup initial value of changed settings, so they can be restored on dialog cancellation - auto connSettingsAboutToChange = - QObject::connect(m_settings, &Settings::aboutToChange, this, [=](Property* property) { + m_connSettingsAboutToChange = m_settings->signalAboutToChange.connectSlot([=](Property* property) { if (m_mapSettingInitialValue.find(property) == m_mapSettingInitialValue.cend()) { const Settings::Variant propertyValue = m_settings->propertyValueConversion().toVariant(*property); m_mapSettingInitialValue.insert({ property, propertyValue}); @@ -220,8 +220,7 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) }); // Synchronize editor widget when value of the corresponding property is changed - auto connSettingsChanged = - QObject::connect(m_settings, &Settings::changed, this, [=](const Property* property) { + m_connSettingsChanged = m_settings->signalChanged.connectSlot([=](const Property* property) { auto itFound = m_mapSettingEditor.find(property); if (itFound != m_mapSettingEditor.cend()) this->syncEditor(itFound->second); @@ -229,13 +228,12 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) // When a setting is clicked in the "right-side" view then scroll to and select corresponding // tree item in the "left-side" view - QObject::connect(m_ui->listWidget_Settings, &QListWidget::currentRowChanged, [=](int listRow) { + QObject::connect(m_ui->listWidget_Settings, &QListWidget::currentRowChanged, this, [=](int listRow) { auto listItem = m_ui->listWidget_Settings->item(listRow); const QVariant variantNodeId = listItem ? listItem->data(ItemSettingNodeId_Role) : QVariant(); const Qt::MatchFlags matchFlags = Qt::MatchRecursive | Qt::MatchExactly; const QModelIndex indexFirst = treeModel->index(0, 0); - const QModelIndexList indexList = treeModel->match( - indexFirst, ItemSettingNodeId_Role, variantNodeId, 1, matchFlags); + const QModelIndexList indexList = treeModel->match(indexFirst, ItemSettingNodeId_Role, variantNodeId, 1, matchFlags); if (!indexList.isEmpty()) { m_ui->treeView_GroupSections->scrollTo(indexList.front(), QAbstractItemView::PositionAtTop); QSignalBlocker _(m_ui->treeView_GroupSections); @@ -259,16 +257,11 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) }); // Action for "Cancel" button : restore changed properties to their initial values - QObject::connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, [=]{ - QObject::disconnect(connSettingsAboutToChange); - QObject::disconnect(connSettingsChanged); - for (const auto& [prop, propInitialValue] : m_mapSettingInitialValue) - m_settings->propertyValueConversion().fromVariant(prop, propInitialValue); - }); + QObject::connect(m_ui->buttonBox, &QDialogButtonBox::rejected, this, &DialogOptions::cancelChanges); // Action for "Restore defaults" button auto btnRestoreDefaults = m_ui->buttonBox->button(QDialogButtonBox::RestoreDefaults); - QObject::connect(btnRestoreDefaults, &QPushButton::clicked, m_settings, &Settings::resetAll); + QObject::connect(btnRestoreDefaults, &QPushButton::clicked, this, [=]{ m_settings->resetAll(); }); // Actions for "Exchange" button auto btnExchange = m_ui->buttonBox->addButton(tr("Exchange"), QDialogButtonBox::ActionRole); @@ -282,10 +275,12 @@ DialogOptions::DialogOptions(Settings* settings, QWidget* parent) DialogOptions::~DialogOptions() { + m_connSettingsAboutToChange.disconnect(); + m_connSettingsChanged.disconnect(); delete m_ui; } -void DialogOptions::setPropertyEditorFactory(std::unique_ptr editorFactory) +void DialogOptions::setPropertyEditorFactory(std::unique_ptr editorFactory) { m_editorFactory = std::move(editorFactory); } @@ -359,12 +354,12 @@ void DialogOptions::loadFromFile() const QFileInfo fi(filepath); if (!fi.exists()) { - WidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("'%1' doesn't exist").arg(filepath)); + QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("'%1' doesn't exist").arg(filepath)); return; } if (!fi.isReadable()) { - WidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("'%1' is not readable").arg(filepath)); + QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("'%1' is not readable").arg(filepath)); return; } @@ -384,7 +379,18 @@ void DialogOptions::saveAs() m_settings->saveAs(&fileSettings, &AppModule::excludeSettingPredicate); fileSettings.sync(); if (fileSettings.get().status() != QSettings::NoError) - WidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("Error when writing to'%1'").arg(filepath)); + QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), tr("Error when writing to'%1'").arg(filepath)); +} + +void DialogOptions::cancelChanges() +{ + m_connSettingsAboutToChange.block(true); + m_connSettingsChanged.block(true); + for (const auto& [prop, propInitialValue] : m_mapSettingInitialValue) + m_settings->propertyValueConversion().fromVariant(prop, propInitialValue); + + m_connSettingsAboutToChange.block(false); + m_connSettingsChanged.block(false); } void DialogOptions::handleTreeViewButtonClick_restoreDefaults(const QModelIndex& index) @@ -405,7 +411,7 @@ void DialogOptions::handleTreeViewButtonClick_restoreDefaults(const QModelIndex& menu->addAction(tr("Restore values for the whole group"), this, [=]{ m_settings->resetGroup(groupIndex); }); - WidgetsUtils::asyncMenuExec(menu); + QtWidgetsUtils::asyncMenuExec(menu); } else { m_settings->resetGroup(groupIndex); diff --git a/src/app/dialog_options.h b/src/app/dialog_options.h index e82185cf..1025ac54 100644 --- a/src/app/dialog_options.h +++ b/src/app/dialog_options.h @@ -22,8 +22,8 @@ class DialogOptions : public QDialog { DialogOptions(Settings* settings, QWidget* parent = nullptr); ~DialogOptions(); - PropertyEditorFactory* editorFactory() const { return m_editorFactory.get(); } - void setPropertyEditorFactory(std::unique_ptr editorFactory); + IPropertyEditorFactory* editorFactory() const { return m_editorFactory.get(); } + void setPropertyEditorFactory(std::unique_ptr editorFactory); private: QWidget* createEditor(Property* property, QWidget* parentWidget) const; @@ -31,14 +31,17 @@ class DialogOptions : public QDialog { void loadFromFile(); void saveAs(); + void cancelChanges(); void handleTreeViewButtonClick_restoreDefaults(const QModelIndex& index); class Ui_DialogOptions* m_ui = nullptr; - std::unique_ptr m_editorFactory; + std::unique_ptr m_editorFactory; std::unordered_map m_mapSettingEditor; std::unordered_map m_mapSettingInitialValue; Settings* m_settings = nullptr; + SignalConnectionHandle m_connSettingsAboutToChange; + SignalConnectionHandle m_connSettingsChanged; }; } // namespace Mayo diff --git a/src/app/dialog_save_image_view.cpp b/src/app/dialog_save_image_view.cpp index c7d01a1d..960229ee 100644 --- a/src/app/dialog_save_image_view.cpp +++ b/src/app/dialog_save_image_view.cpp @@ -6,7 +6,7 @@ #include "dialog_save_image_view.h" #include "ui_dialog_save_image_view.h" -#include "widgets_utils.h" +#include "qtwidgets_utils.h" #include "../graphics/graphics_utils.h" #include @@ -33,7 +33,7 @@ static QImage qtImageTemp(const Image_PixMap& occImg) } // namespace Internal -DialogSaveImageView::DialogSaveImageView(const Handle_V3d_View& view, QWidget *parent) +DialogSaveImageView::DialogSaveImageView(const Handle_V3d_View& view, QWidget* parent) : QDialog(parent), m_ui(new Ui_DialogSaveImageView), m_view(view) @@ -97,7 +97,7 @@ void DialogSaveImageView::saveFile() saveOk = img.save(fileName, format); } if (!saveOk) { - WidgetsUtils::asyncMsgBoxCritical( + QtWidgetsUtils::asyncMsgBoxCritical( this, tr("Error"), tr("Failed to save image '%1'").arg(fileName)); } } diff --git a/src/app/dialog_save_image_view.h b/src/app/dialog_save_image_view.h index f368d279..66162971 100644 --- a/src/app/dialog_save_image_view.h +++ b/src/app/dialog_save_image_view.h @@ -16,7 +16,7 @@ namespace Mayo { class DialogSaveImageView : public QDialog { Q_OBJECT public: - DialogSaveImageView(const Handle_V3d_View& view, QWidget *parent = nullptr); + DialogSaveImageView(const Handle_V3d_View& view, QWidget* parent = nullptr); ~DialogSaveImageView(); private: diff --git a/src/app/dialog_task_manager.cpp b/src/app/dialog_task_manager.cpp index 9ae45309..fc195275 100644 --- a/src/app/dialog_task_manager.cpp +++ b/src/app/dialog_task_manager.cpp @@ -116,10 +116,10 @@ DialogTaskManager::DialogTaskManager(TaskManager* taskMgr, QWidget* parent) this->setWindowFlags(Qt::Dialog | Qt::CustomizeWindowHint | Qt::WindowTitleHint); this->setWindowModality(Qt::WindowModal); - QObject::connect(taskMgr, &TaskManager::started, this, &DialogTaskManager::onTaskStarted); - QObject::connect(taskMgr, &TaskManager::ended, this, &DialogTaskManager::onTaskEnded); - QObject::connect(taskMgr, &TaskManager::progressChanged, this, &DialogTaskManager::onTaskProgress); - QObject::connect(taskMgr, &TaskManager::progressStep, this, &DialogTaskManager::onTaskProgressStep); + taskMgr->signalStarted.connectSlot(&DialogTaskManager::onTaskStarted, this); + taskMgr->signalEnded.connectSlot(&DialogTaskManager::onTaskEnded, this); + taskMgr->signalProgressChanged.connectSlot(&DialogTaskManager::onTaskProgress, this); + taskMgr->signalProgressStep.connectSlot(&DialogTaskManager::onTaskProgressStep, this); } DialogTaskManager::~DialogTaskManager() @@ -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()->locale(); + const QLocale& locale = AppModule::get()->qtLocale(); if (!text.isEmpty()) QStringUtils::append(&text, tr(" / "), locale); diff --git a/src/app/document_property_group.cpp b/src/app/document_property_group.cpp index 3ed202c3..24b987b7 100644 --- a/src/app/document_property_group.cpp +++ b/src/app/document_property_group.cpp @@ -23,12 +23,12 @@ DocumentPropertyGroup::DocumentPropertyGroup(const DocumentPtr& doc) 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 QString qstrFileSize = QStringUtils::bytesText(fileSize, appModule->qtLocale()); + this->strFileSize.setValue(to_stdString(qstrFileSize)); 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); + const QString strCreated = appModule->qtLocale().toString(fileInfo.birthTime(), QLocale::ShortFormat); + const QString strModified = appModule->qtLocale().toString(fileInfo.lastModified(), QLocale::ShortFormat); this->strCreatedDateTime.setValue(to_stdString(strCreated)); this->strModifiedDateTime.setValue(to_stdString(strModified)); diff --git a/src/app/document_property_group.h b/src/app/document_property_group.h index 6d8cfdec..d6a35eff 100644 --- a/src/app/document_property_group.h +++ b/src/app/document_property_group.h @@ -11,6 +11,8 @@ namespace Mayo { +// Provides relevant properties for a Document object +// TODO Connect to Document "changed" signals to update the properties class DocumentPropertyGroup : public PropertyGroup { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::DocumentPropertyGroup) public: diff --git a/src/app/document_tree_node_properties_providers.cpp b/src/app/document_tree_node_properties_providers.cpp index 4a277d45..94db4dcc 100644 --- a/src/app/document_tree_node_properties_providers.cpp +++ b/src/app/document_tree_node_properties_providers.cpp @@ -7,15 +7,23 @@ #include "document_tree_node_properties_providers.h" #include "../base/caf_utils.h" -#include "../base/data_triangulation.h" +#include "../base/label_data.h" +#include "../base/triangulation_annex_data.h" #include "../base/document.h" #include "../base/document_tree_node.h" +#include "../base/mesh_access.h" #include "../base/mesh_utils.h" #include "../base/meta_enum.h" +#include "../base/point_cloud_data.h" #include "../base/xcaf.h" +#include "../graphics/graphics_mesh_object_driver.h" +#include "../graphics/graphics_point_cloud_object_driver.h" +#include "../graphics/graphics_shape_object_driver.h" #include "qstring_conv.h" +#include #include +#include namespace Mayo { @@ -194,7 +202,7 @@ class XCaf_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroup bool XCaf_DocumentTreeNodePropertiesProvider::supports(const DocumentTreeNode& treeNode) const { - return XCaf::isShape(treeNode.label()); + return GraphicsShapeObjectDriver::shapeSupportStatus(treeNode.label()) == GraphicsObjectDriver::Support::Complete; } std::unique_ptr @@ -211,15 +219,15 @@ class Mesh_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroup public: Properties(const DocumentTreeNode& treeNode) { - 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_SquareMillimeter); - m_propertyVolume.setQuantity(MeshUtils::triangulationVolume(polyTri) * Quantity_CubicMillimeter); + Handle_Poly_Triangulation mesh; + IMeshAccess_visitMeshes(treeNode, [&](const IMeshAccess& access) { + mesh = access.triangulation(); + }); + + m_propertyNodeCount.setValue(!mesh.IsNull() ? mesh->NbNodes() : 0); + m_propertyTriangleCount.setValue(!mesh.IsNull() ? mesh->NbTriangles() : 0); + m_propertyArea.setQuantity(MeshUtils::triangulationArea(mesh) * Quantity_SquareMillimeter); + m_propertyVolume.setQuantity(MeshUtils::triangulationVolume(mesh) * Quantity_CubicMillimeter); for (Property* property : this->properties()) property->setUserReadOnly(true); } @@ -232,7 +240,7 @@ class Mesh_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroup bool Mesh_DocumentTreeNodePropertiesProvider::supports(const DocumentTreeNode& treeNode) const { - return CafUtils::hasAttribute(treeNode.label()); + return GraphicsMeshObjectDriver::meshSupportStatus(treeNode.label()) == GraphicsObjectDriver::Support::Complete; } std::unique_ptr @@ -244,4 +252,48 @@ Mesh_DocumentTreeNodePropertiesProvider::properties(const DocumentTreeNode& tree return std::make_unique(treeNode); } +class PointCloud_DocumentTreeNodePropertiesProvider::Properties : public PropertyGroupSignals { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::PointCloud_DocumentTreeNodeProperties) +public: + Properties(const DocumentTreeNode& treeNode) + { + auto attrPointCloudData = CafUtils::findAttribute(treeNode.label()); + + const bool hasAttrData = !attrPointCloudData.IsNull() && !attrPointCloudData->points().IsNull(); + m_propertyPointCount.setValue(hasAttrData ? attrPointCloudData->points()->VertexNumber() : 0); + m_propertyHasColors.setValue(hasAttrData ? attrPointCloudData->points()->HasVertexColors() : false); + if (hasAttrData) { + Bnd_Box bndBox; + const int pntCount = attrPointCloudData->points()->VertexNumber(); + for (int i = 1; i <= pntCount; ++i) + bndBox.Add(attrPointCloudData->points()->Vertice(i)); + + m_propertyCornerMin.setValue(bndBox.CornerMin()); + m_propertyCornerMax.setValue(bndBox.CornerMax()); + } + + for (Property* property : this->properties()) + property->setUserReadOnly(true); + } + + PropertyInt m_propertyPointCount{ this, textId("PointCount") }; + PropertyBool m_propertyHasColors{ this, textId("HasColors") }; + PropertyOccPnt m_propertyCornerMin{ this, textId("CornerMin") }; + PropertyOccPnt m_propertyCornerMax{ this, textId("CornerMax") }; +}; + +bool PointCloud_DocumentTreeNodePropertiesProvider::supports(const DocumentTreeNode& treeNode) const +{ + return GraphicsPointCloudObjectDriver::pointCloudSupportStatus(treeNode.label()) == GraphicsObjectDriver::Support::Complete; +} + +std::unique_ptr +PointCloud_DocumentTreeNodePropertiesProvider::properties(const DocumentTreeNode& treeNode) const +{ + if (!treeNode.isValid()) + return {}; + + return std::make_unique(treeNode); +} + } // namespace Mayo diff --git a/src/app/document_tree_node_properties_providers.h b/src/app/document_tree_node_properties_providers.h index 29c3d0ef..90fd1dde 100644 --- a/src/app/document_tree_node_properties_providers.h +++ b/src/app/document_tree_node_properties_providers.h @@ -13,6 +13,7 @@ namespace Mayo { +// Provides relevant properties for tree node pointing to XCAF data class XCaf_DocumentTreeNodePropertiesProvider : public DocumentTreeNodePropertiesProvider { public: bool supports(const DocumentTreeNode& treeNode) const override; @@ -22,6 +23,7 @@ class XCaf_DocumentTreeNodePropertiesProvider : public DocumentTreeNodePropertie class Properties; }; +// Provides relevant properties for tree node pointing to mesh data class Mesh_DocumentTreeNodePropertiesProvider : public DocumentTreeNodePropertiesProvider { public: bool supports(const DocumentTreeNode& treeNode) const override; @@ -31,4 +33,13 @@ class Mesh_DocumentTreeNodePropertiesProvider : public DocumentTreeNodePropertie class Properties; }; +class PointCloud_DocumentTreeNodePropertiesProvider : public DocumentTreeNodePropertiesProvider { +public: + bool supports(const DocumentTreeNode& treeNode) const override; + std::unique_ptr properties(const DocumentTreeNode& treeNode) const override; + +private: + class Properties; +}; + } // namespace Mayo diff --git a/src/app/filepath_conv.h b/src/app/filepath_conv.h index 7743c071..4e178d8d 100644 --- a/src/app/filepath_conv.h +++ b/src/app/filepath_conv.h @@ -14,7 +14,7 @@ namespace Mayo { // Returns a FilePath object constructed from input -inline FilePath filepathFrom(const QByteArray& bytes) { return std::filesystem::u8path(bytes.constData()); } +inline FilePath filepathFrom(const QByteArray& bytes) { return filepathFrom(std::string_view{ bytes.constData() }); } inline FilePath filepathFrom(const QString& str) { return reinterpret_cast(str.utf16()); } inline FilePath filepathFrom(const QFileInfo& fi) { return filepathFrom(fi.filePath()); } diff --git a/src/app/grid_helper.cpp b/src/app/grid_helper.cpp index 40bf4673..b3605bf2 100644 --- a/src/app/grid_helper.cpp +++ b/src/app/grid_helper.cpp @@ -32,25 +32,32 @@ void ProxyModel::setSourceModel(QAbstractItemModel* newModel) }); QObject::connect( newModel, &QAbstractItemModel::modelAboutToBeReset, - this, &ProxyModel::beginResetModel); + this, &ProxyModel::beginResetModel + ); QObject::connect( newModel, &QAbstractItemModel::modelReset, - this, &ProxyModel::endResetModel); + this, &ProxyModel::endResetModel + ); QObject::connect( newModel, &QAbstractItemModel::rowsAboutToBeInserted, - this, &ProxyModel::beginResetModel); + this, &ProxyModel::beginResetModel + ); QObject::connect( newModel, &QAbstractItemModel::rowsInserted, - this, &ProxyModel::endResetModel); + this, &ProxyModel::endResetModel + ); QObject::connect( newModel, &QAbstractItemModel::rowsAboutToBeRemoved, - this, &ProxyModel::beginResetModel); + this, &ProxyModel::beginResetModel + ); QObject::connect( newModel, &QAbstractItemModel::rowsRemoved, - this, &ProxyModel::endResetModel); + this, &ProxyModel::endResetModel + ); QObject::connect( newModel, &QAbstractItemModel::dataChanged, - this, &ProxyModel::onDataChanged); + this, &ProxyModel::onDataChanged + ); } } diff --git a/src/app/grid_helper.h b/src/app/grid_helper.h index 87ce4fcf..c276477f 100644 --- a/src/app/grid_helper.h +++ b/src/app/grid_helper.h @@ -10,8 +10,11 @@ #include namespace Mayo { + +// Provides helper tools for "grid" view namespace GridHelper { +// Provides a proxy model to layout items along a grid(this setColumnCount()) class ProxyModel : public QAbstractItemModel { public: void setSourceModel(QAbstractItemModel* newModel); @@ -32,12 +35,14 @@ class ProxyModel : public QAbstractItemModel { private: void onDataChanged( - const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles); + const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector& roles + ); QAbstractItemModel* m_sourceModel = nullptr; int m_columnCount = 1; }; +// Provides Qt view with grid layout class View : public QTableView { public: View(QWidget* parent); diff --git a/src/gui/gui_document_list_model.cpp b/src/app/gui_document_list_model.cpp similarity index 82% rename from src/gui/gui_document_list_model.cpp rename to src/app/gui_document_list_model.cpp index 677d7c33..5bbd27c3 100644 --- a/src/gui/gui_document_list_model.cpp +++ b/src/app/gui_document_list_model.cpp @@ -8,8 +8,8 @@ #include "../base/application.h" #include "../base/document.h" -#include "gui_application.h" -#include "gui_document.h" +#include "../gui/gui_application.h" +#include "../gui/gui_document.h" namespace Mayo { @@ -19,15 +19,10 @@ GuiDocumentListModel::GuiDocumentListModel(const GuiApplication* guiApp, QObject for (const GuiDocument* doc : guiApp->guiDocuments()) this->appendGuiDocument(doc); - QObject::connect( - guiApp, &GuiApplication::guiDocumentAdded, - this, &GuiDocumentListModel::appendGuiDocument); - QObject::connect( - guiApp, &GuiApplication::guiDocumentErased, - this, &GuiDocumentListModel::removeGuiDocument); - QObject::connect( - guiApp->application().get(), &Application::documentNameChanged, - this, &GuiDocumentListModel::onDocumentNameChanged); + auto app = guiApp->application(); + app->signalDocumentNameChanged.connectSlot(&GuiDocumentListModel::onDocumentNameChanged, this); + guiApp->signalGuiDocumentAdded.connectSlot(&GuiDocumentListModel::appendGuiDocument, this); + guiApp->signalGuiDocumentErased.connectSlot(&GuiDocumentListModel::removeGuiDocument, this); } QVariant GuiDocumentListModel::data(const QModelIndex& index, int role) const diff --git a/src/gui/gui_document_list_model.h b/src/app/gui_document_list_model.h similarity index 86% rename from src/gui/gui_document_list_model.h rename to src/app/gui_document_list_model.h index 86d95437..06a5a569 100644 --- a/src/gui/gui_document_list_model.h +++ b/src/app/gui_document_list_model.h @@ -15,6 +15,8 @@ namespace Mayo { class GuiApplication; class GuiDocument; +// Provides a Qt item model of the documents owned by a GuiApplication object +// Contents is automatically updated in reaction of Application and GuiApplication signals class GuiDocumentListModel : public QAbstractListModel { public: GuiDocumentListModel(const GuiApplication* guiApp, QObject* parent = nullptr); diff --git a/src/app/item_view_buttons.cpp b/src/app/item_view_buttons.cpp index 28065cfe..c2c54374 100644 --- a/src/app/item_view_buttons.cpp +++ b/src/app/item_view_buttons.cpp @@ -20,8 +20,8 @@ namespace Mayo { namespace { -template -void checkedAssign(VALUE_TYPE CLASS::*attrMember, CLASS* object, CALL_VALUE_TYPE value) +template +void checkedAssign(ValueType ClassType::*attrMember, ClassType* object, CallValueType value) { if (object && attrMember) object->*attrMember = value; @@ -174,7 +174,7 @@ void ItemViewButtons::Private::resetButtonUnderMouseState() * * \param btnId Identifier of the button clicked (this is the id that was * passed to addButton()) - * \param index Index of the item model where the button click occured + * \param index Index of the item model where the button click occurred */ ItemViewButtons::ItemViewButtons(QAbstractItemView* view, QObject* parent) diff --git a/src/app/list_helper.cpp b/src/app/list_helper.cpp index 1f595a81..c68ca10e 100644 --- a/src/app/list_helper.cpp +++ b/src/app/list_helper.cpp @@ -6,7 +6,7 @@ #include "list_helper.h" -#include "../gui/qtgui_utils.h" +#include "qtgui_utils.h" #include #include #include @@ -97,12 +97,11 @@ void Model::setStorage(std::unique_ptr ptr) } ItemDelegate::ItemDelegate(QObject* parent) - : QStyledItemDelegate(parent) + : QStyledItemDelegate(parent), + m_frameColor(220, 220, 220), + m_pixmapColor(qApp->palette().color(QPalette::Button)), + m_textColor(qApp->palette().color(QPalette::WindowText)) { - m_frameColor = QColor(220, 220, 220); - m_pixmapColor = qApp->palette().color(QPalette::Button); - m_textColor = qApp->palette().color(QPalette::WindowText); - m_itemAnimation.setDuration(250); m_itemAnimation.setEasingCurve(QEasingCurve::OutQuad); m_itemAnimation.setLoopCount(1); @@ -110,7 +109,8 @@ ItemDelegate::ItemDelegate(QObject* parent) m_itemAnimation.setEndValue(m_itemSize.height()); QObject::connect( &m_itemAnimation, &QVariantAnimation::valueChanged, - this, &ItemDelegate::drawItem); + this, &ItemDelegate::drawItem + ); } void ItemDelegate::paint( diff --git a/src/app/list_helper.h b/src/app/list_helper.h index 02caafd6..81c02013 100644 --- a/src/app/list_helper.h +++ b/src/app/list_helper.h @@ -30,12 +30,12 @@ struct ModelStorage { virtual const ModelItem* at(int i) const = 0; }; -template struct DefaultModelStorage : public ModelStorage { - static_assert(!std::is_pointer::value); - static_assert(std::is_base_of::value); +template struct DefaultModelStorage : public ModelStorage { + static_assert(!std::is_pointer::value); + static_assert(std::is_base_of::value); int count() const override { return int(m_items.size()); } - const ITEM* at(int i) const override { return &m_items.at(i); } - std::vector m_items; + const ItemType* at(int i) const override { return &m_items.at(i); } + std::vector m_items; }; class Model : public QAbstractListModel { @@ -105,7 +105,7 @@ class ItemDelegate : public QStyledItemDelegate { mutable QModelIndex m_previousIndex; mutable QVariantAnimation m_itemAnimation; mutable QRect m_area; - mutable QAbstractItemView* m_widget; + mutable QAbstractItemView* m_widget = nullptr; mutable QPixmap m_blurredPixmap; }; diff --git a/src/app/main.cpp b/src/app/main.cpp index a6da0314..76e4b510 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -12,12 +12,14 @@ #include "../io_gmio/io_gmio.h" #include "../io_image/io_image.h" #include "../io_occ/io_occ.h" +#include "../io_off/io_off_reader.h" +#include "../io_off/io_off_writer.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_point_cloud_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" @@ -26,6 +28,7 @@ #include "mainwindow.h" #include "qsettings_storage.h" #include "qstring_conv.h" +#include "qtgui_utils.h" #include "theme.h" #include "version.h" #include "widget_model_tree.h" @@ -62,6 +65,7 @@ namespace Mayo { // Declared in graphics/graphics_create_driver.cpp void setFunctionCreateGraphicsDriver(std::function fn); +// Provides an i18n context for the current file(main.cpp) class Main { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::Main) Q_DECLARE_TR_FUNCTIONS(Mayo::Main) @@ -69,6 +73,7 @@ class Main { namespace { +// Stores arguments(options) passed at command line struct CommandLineArguments { QString themeName; FilePath filepathSettings; @@ -79,6 +84,7 @@ struct CommandLineArguments { bool cliProgressReport = true; }; +// Provides customization of Qt message handler class LogMessageHandler { public: static LogMessageHandler& instance() @@ -87,15 +93,20 @@ class LogMessageHandler { return object; } + // Corresponds to CommandLineArguments::includeDebugLogs void enableDebugLogs(bool on) { m_enableDebugLogs = on; } + // Corresponds to CommandLineArguments::filepathLog void setOutputFilePath(const FilePath& fp) { m_outputFilePath = fp; - m_outputFile.open(fp, std::ios::out | std::ios::app); + if (!fp.empty()) + m_outputFile.open(fp, std::ios::out | std::ios::app); + else + m_outputFile.close(); } std::ostream& outputStream(QtMsgType type) @@ -109,6 +120,7 @@ class LogMessageHandler { return std::cerr; } + // Function called for Qt message handling static void qtHandler(QtMsgType type, const QMessageLogContext& /*context*/, const QString& msg) { const std::string localMsg = consoleToPrintable(msg); @@ -142,8 +154,27 @@ class LogMessageHandler { bool m_enableDebugLogs = true; }; +// Provides handling of signal/slot thread mismatch with the help of Qt +// There will be a single QObject created per thread, so it can be used to enqueue slot functions +class QtSignalThreadHelper : public ISignalThreadHelper { +public: + std::any getCurrentThreadContext() override + { + // Note: thread_local implies "static" + // See https://en.cppreference.com/w/cpp/language/storage_duration + thread_local QObject obj; + return &obj; + } + + void execInThread(const std::any& context, const std::function& fn) override + { + QTimer::singleShot(0, std::any_cast(context), fn); + } +}; + } // namespace +// Parses command line and process Qt builtin options(basically --version and --help) static CommandLineArguments processCommandLine() { CommandLineArguments args; @@ -151,54 +182,63 @@ static CommandLineArguments processCommandLine() // Configure command-line parser QCommandLineParser cmdParser; cmdParser.setApplicationDescription( - Main::tr("Mayo the opensource 3D CAD viewer and converter")); + Main::tr("Mayo the opensource 3D CAD viewer and converter") + ); cmdParser.addHelpOption(); cmdParser.addVersionOption(); const QCommandLineOption cmdOptionTheme( QStringList{ "t", "theme" }, Main::tr("Theme for the UI(classic|dark)"), - Main::tr("name")); + Main::tr("name") + ); cmdParser.addOption(cmdOptionTheme); const QCommandLineOption cmdFileSettings( QStringList{ "s", "settings" }, Main::tr("Settings file(INI format) to load at startup"), - Main::tr("filepath")); + Main::tr("filepath") + ); cmdParser.addOption(cmdFileSettings); const QCommandLineOption cmdFileToExport( QStringList{ "e", "export" }, Main::tr("Export opened files into an output file, can be repeated for different " "formats(eg. -e file.stp -e file.igs...)"), - Main::tr("filepath")); + Main::tr("filepath") + ); cmdParser.addOption(cmdFileToExport); const QCommandLineOption cmdFileLog( QStringList{ "log-file" }, Main::tr("Writes log messages into output file"), - Main::tr("filepath")); + Main::tr("filepath") + ); cmdParser.addOption(cmdFileLog); const QCommandLineOption cmdDebugLogs( QStringList{ "debug-logs" }, - Main::tr("Don't filter out debug log messages in release build")); + Main::tr("Don't filter out debug log messages in release build") + ); cmdParser.addOption(cmdDebugLogs); const QCommandLineOption cmdCliNoProgress( QStringList{ "no-progress" }, - Main::tr("Disable progress reporting in console output(CLI-mode only)")); + Main::tr("Disable progress reporting in console output(CLI-mode only)") + ); cmdParser.addOption(cmdCliNoProgress); cmdParser.addPositionalArgument( Main::tr("files"), Main::tr("Files to open at startup, optionally"), - Main::tr("[files...]")); + Main::tr("[files...]") + ); #ifdef MAYO_WITH_TESTS const QCommandLineOption cmdRunTests( QStringList{ "runtests" }, - Main::tr("Execute unit tests and exit application")); + Main::tr("Execute unit tests and exit application") + ); cmdParser.addOption(cmdRunTests); #endif @@ -240,6 +280,7 @@ Theme* mayoTheme() return globalTheme.get(); } +// Set OpenCascade environment variables defined in a settings file(INI format) static void initOpenCascadeEnvironment(const FilePath& settingsFilepath) { const QString strSettingsFilepath = filepathTo(settingsFilepath); @@ -281,11 +322,12 @@ static void initOpenCascadeEnvironment(const FilePath& settingsFilepath) } } +// Function called by the Application i18n system, see Application::addTranslator() static std::string_view qtTranslate(const TextId& text, int n) { const QString qstr = QCoreApplication::translate(text.trContext.data(), text.key.data(), nullptr, n); auto qstrHash = qHash(qstr); - static std::unordered_map mapStr; + static std::unordered_map mapStr; static QReadWriteLock mapStrLock; { QReadLocker locker(&mapStrLock); @@ -347,8 +389,11 @@ static void initGui(GuiApplication* guiApp) IWidgetOccView::setCreator(&QWidgetOccView::create); // Use QOpenGLWidget if possible -#if OCC_VERSION_HEX >= 0x070600 && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) - if (qobject_cast(QCoreApplication::instance())) { // QOpenGL requires QGuiApplication +#if OCC_VERSION_HEX >= 0x070600 + const auto& propForceOpenGlFallbackWidget = AppModule::get()->properties()->forceOpenGlFallbackWidget; + AppModule::get()->settings()->loadProperty(&propForceOpenGlFallbackWidget); + const bool hasQGuiApplication = qobject_cast(QCoreApplication::instance()); + if (!propForceOpenGlFallbackWidget && hasQGuiApplication) { // QOpenGL requires QGuiApplication const std::string strGlVersion = queryGlVersionString(); const QVersionNumber glVersion = parseSemanticVersionString(strGlVersion); qInfo() << fmt::format("OpenGL v{}.{}", glVersion.majorVersion(), glVersion.minorVersion()).c_str(); @@ -365,9 +410,9 @@ static void initGui(GuiApplication* guiApp) // Register Graphics entity drivers guiApp->addGraphicsObjectDriver(std::make_unique()); guiApp->addGraphicsObjectDriver(std::make_unique()); + guiApp->addGraphicsObjectDriver(std::make_unique()); } - // Initializes and runs Mayo application static int runApp(QCoreApplication* qtApp) { @@ -395,10 +440,12 @@ static int runApp(QCoreApplication* qtApp) } }; + // Signals + setGlobalSignalThreadHelper(std::make_unique()); + // Message logging LogMessageHandler::instance().enableDebugLogs(args.includeDebugLogs); - if (!args.filepathLog.empty()) - LogMessageHandler::instance().setOutputFilePath(args.filepathLog); + LogMessageHandler::instance().setOutputFilePath(args.filepathLog); // Initialize AppModule auto appModule = AppModule::get(); @@ -425,13 +472,16 @@ static int runApp(QCoreApplication* qtApp) // Register providers to query document tree node properties appModule->addPropertiesProvider(std::make_unique()); 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->addFactoryReader(std::make_unique()); ioSystem->addFactoryReader(std::make_unique()); ioSystem->addFactoryWriter(std::make_unique()); + ioSystem->addFactoryWriter(std::make_unique()); ioSystem->addFactoryWriter(std::make_unique()); ioSystem->addFactoryWriter(IO::GmioFactoryWriter::create()); ioSystem->addFactoryWriter(std::make_unique(guiApp)); @@ -458,9 +508,7 @@ static int runApp(QCoreApplication* qtApp) } // Record recent files when documents are closed - QObject::connect( - guiApp, &GuiApplication::guiDocumentErased, - AppModule::get(), &AppModule::recordRecentFileThumbnail); + guiApp->signalGuiDocumentErased.connectSlot(&AppModule::recordRecentFileThumbnail, AppModule::get()); // Register WidgetModelTreeBuilter prototypes WidgetModelTree::addPrototypeBuilder(std::make_unique()); @@ -485,7 +533,7 @@ static int runApp(QCoreApplication* qtApp) mainWindow.setWindowTitle(QCoreApplication::applicationName()); mainWindow.show(); if (!args.listFilepathToOpen.empty()) { - QTimer::singleShot(0, [&]{ mainWindow.openDocumentsFromList(args.listFilepathToOpen); }); + QTimer::singleShot(0, qtApp, [&]{ mainWindow.openDocumentsFromList(args.listFilepathToOpen); }); } appModule->settings()->resetAll(); @@ -496,47 +544,60 @@ static int runApp(QCoreApplication* qtApp) return code; } -static bool isAppCliMode = false; -static void onQtAppExit() -{ -#if defined(Q_OS_WIN) && defined(NDEBUG) - if (isAppCliMode) - consoleSendEnterKey(); -#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[]) { qInstallMessageHandler(&Mayo::LogMessageHandler::qtHandler); - qAddPostRoutine(&Mayo::onQtAppExit); - // Running CLI mode? - for (int i = 1; i < argc && !Mayo::isAppCliMode; ++i) { - static const char* cliArgs[] = { "-e", "--export", "-h", "--help", "-v", "--version" }; - auto itCliArg = std::find_if(std::cbegin(cliArgs), std::cend(cliArgs), [=](const char* cliArg) { - return std::strcmp(argv[i], cliArg) == 0; - }); - Mayo::isAppCliMode = itCliArg != std::cend(cliArgs); - } + // OpenCascade TKOpenGl depends on XLib for Linux(excepting Android) and BSD systems(excepting macOS) + // See for example implementation of Aspect_DisplayConnection where XLib is explicitly used + // On systems running eg Wayland this would cause problems(see https://github.com/fougue/mayo/issues/178) + // As a workaround the Qt platform is forced to xcb +#if (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) || (defined(Q_OS_BSD4) && !defined(Q_OS_MACOS)) + qputenv("QT_QPA_PLATFORM", "xcb"); +#endif + // Helper function to check if application arguments contain any option listed in 'listOption' + auto fnArgsContainAnyOf = [=](std::initializer_list listOption) { + for (int i = 1; i < argc; ++i) { + for (const char* option : listOption) { + if (std::strcmp(argv[i], option) == 0) + return true; + } + } + return false; + }; + + // Configure and create Qt application object #if defined(Q_OS_WIN) // Never use ANGLE on Windows, since OCCT 3D Viewer does not expect this QCoreApplication::setAttribute(Qt::AA_UseDesktopOpenGL); #endif - + QCoreApplication::setOrganizationName("Fougue Ltd"); + QCoreApplication::setOrganizationDomain("www.fougue.pro"); + QCoreApplication::setApplicationName("Mayo"); + QCoreApplication::setApplicationVersion(QString::fromUtf8(Mayo::strVersion)); + const bool isAppCliMode = fnArgsContainAnyOf({ "-e", "--export", "-h", "--help", "-v", "--version" }); std::unique_ptr ptrApp( - Mayo::isAppCliMode ? new QCoreApplication(argc, argv) : new QApplication(argc, argv) + isAppCliMode ? new QCoreApplication(argc, argv) : new QApplication(argc, argv) ); + //QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + + // Handle unit tests +#ifdef MAYO_WITH_TESTS + if (fnArgsContainAnyOf({ "--runtests" })) + return Mayo::runTests(argc, argv); +#endif + + // Configure for CLI mode + if (isAppCliMode) { #if defined(Q_OS_WIN) && defined(NDEBUG) - if (Mayo::isAppCliMode) { + qAddPostRoutine(&Mayo::consoleSendEnterKey); // https://devblogs.microsoft.com/oldnewthing/20090101-00/?p=19643 // https://www.tillett.info/2013/05/13/how-to-create-a-windows-program-that-works-as-both-as-a-gui-and-console-application/ if (AttachConsole(ATTACH_PARENT_PROCESS) || AllocConsole()) { @@ -550,20 +611,9 @@ int main(int argc, char* argv[]) fnRedirectToConsole(STD_ERROR_HANDLE, stderr, "CONOUT$"); std::ios::sync_with_stdio(); } - } #endif - - QCoreApplication::setOrganizationName("Fougue Ltd"); - 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 + // Run Mayo application in CLI or GUI mode return Mayo::runApp(ptrApp.get()); } diff --git a/src/app/mainwindow.cpp b/src/app/mainwindow.cpp index e649f0c2..ce0c4531 100644 --- a/src/app/mainwindow.cpp +++ b/src/app/mainwindow.cpp @@ -9,202 +9,50 @@ #include "../base/application.h" #include "../base/application_item_selection_model.h" -#include "../base/cpp_utils.h" #include "../base/document.h" #include "../base/global.h" -#include "../base/io_format.h" -#include "../base/io_system.h" #include "../base/messenger.h" #include "../base/settings.h" -#include "../base/task_manager.h" #include "../graphics/graphics_object_driver.h" #include "../graphics/graphics_utils.h" #include "../gui/gui_application.h" #include "../gui/gui_document.h" -#include "../gui/gui_document_list_model.h" -#include "../gui/qtgui_utils.h" +#include "app_context.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 "commands_file.h" +#include "commands_display.h" +#include "commands_tools.h" +#include "commands_window.h" +#include "commands_help.h" #include "dialog_task_manager.h" #include "document_property_group.h" -#include "document_tree_node_properties_providers.h" #include "filepath_conv.h" +#include "gui_document_list_model.h" #include "item_view_buttons.h" #include "qstring_conv.h" +#include "qtgui_utils.h" +#include "qtwidgets_utils.h" #include "theme.h" #include "widget_file_system.h" #include "widget_gui_document.h" #include "widget_message_indicator.h" #include "widget_model_tree.h" #include "widget_occ_view.h" -#include "widget_occ_view_controller.h" #include "widget_properties_editor.h" -#include "widgets_utils.h" #ifdef Q_OS_WIN # include "windows/win_taskbar_global_progress.h" #endif -#include -#include -#include #include -#include -#include -#include -#include // WARNING Qt5 / Qt6 -#include -#include #include -#include -#include - namespace Mayo { -namespace Internal { - -static QString fileFilter(IO::Format format) -{ - if (format == IO::Format_Unknown) - return {}; - - QString filter; - for (std::string_view suffix : IO::formatFileSuffixes(format)) { - if (suffix.data() != IO::formatFileSuffixes(format).front().data()) - filter += " "; - - const QString qsuffix = to_QString(suffix); - filter += "*." + qsuffix; -#ifdef Q_OS_UNIX - filter += " *." + qsuffix.toUpper(); -#endif - } - - //: %1 is the format identifier and %2 is the file filters string - return MainWindow::tr("%1 files(%2)") - .arg(to_QString(IO::formatIdentifier(format))) - .arg(filter); -} - -static IO::Format formatFromFilter(const QString& filter) -{ - for (IO::Format format : AppModule::get()->ioSystem()->readerFormats()) { - if (filter == fileFilter(format)) - return format; - } - - for (IO::Format format : AppModule::get()->ioSystem()->writerFormats()) { - if (filter == fileFilter(format)) - return format; - } - - return IO::Format_Unknown; -} - -// TODO: move in Options -struct ImportExportSettings { - FilePath openDir; - QString selectedFilter; - - static ImportExportSettings load() - { - return { - AppModule::get()->properties()->lastOpenDir.value(), - to_QString(AppModule::get()->properties()->lastSelectedFormatFilter.value()) - }; - } - - static void save(const ImportExportSettings& sets) - { - AppModule::get()->properties()->lastOpenDir.setValue(sets.openDir); - AppModule::get()->properties()->lastSelectedFormatFilter.setValue(to_stdString(sets.selectedFilter)); - } -}; - -struct OpenFileNames { - std::vector listFilepath; - ImportExportSettings lastIoSettings; - IO::Format selectedFormat; - - enum GetOption { - GetOne, - GetMany - }; - - static OpenFileNames get( - QWidget* parentWidget, - OpenFileNames::GetOption option = OpenFileNames::GetMany) - { - OpenFileNames result; - result.selectedFormat = IO::Format_Unknown; - result.lastIoSettings = ImportExportSettings::load(); - QStringList listFormatFilter; - for (IO::Format format : AppModule::get()->ioSystem()->readerFormats()) - listFormatFilter += fileFilter(format); - - const QString allFilesFilter = MainWindow::tr("All files(*.*)"); - listFormatFilter.append(allFilesFilter); - const QString dlgTitle = MainWindow::tr("Select Part File"); - const QString dlgOpenDir = filepathTo(result.lastIoSettings.openDir); - const QString dlgFilter = listFormatFilter.join(QLatin1String(";;")); - QString* dlgPtrSelFilter = &result.lastIoSettings.selectedFilter; - if (option == OpenFileNames::GetOne) { - const QString strFilepath = - QFileDialog::getOpenFileName( - parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); - result.listFilepath.clear(); - result.listFilepath.push_back(filepathFrom(strFilepath)); - } - else { - const QStringList listStrFilePath = - QFileDialog::getOpenFileNames( - parentWidget, dlgTitle, dlgOpenDir, dlgFilter, dlgPtrSelFilter); - result.listFilepath.clear(); - for (const QString& strFilePath : listStrFilePath) - result.listFilepath.push_back(filepathFrom(strFilePath)); - } - - if (!result.listFilepath.empty()) { - result.lastIoSettings.openDir = result.listFilepath.front(); - result.selectedFormat = - result.lastIoSettings.selectedFilter != allFilesFilter ? - formatFromFilter(result.lastIoSettings.selectedFilter) : - IO::Format_Unknown; - ImportExportSettings::save(result.lastIoSettings); - } - - return result; - } -}; - -static void handleMessage(Messenger::MessageType msgType, const QString& text, QWidget* mainWnd) -{ - switch (msgType) { - case Messenger::MessageType::Trace: - break; - case Messenger::MessageType::Info: - WidgetMessageIndicator::showInfo(text, mainWnd); - break; - case Messenger::MessageType::Warning: - WidgetsUtils::asyncMsgBoxWarning(mainWnd, MainWindow::tr("Warning"), text); - break; - case Messenger::MessageType::Error: - WidgetsUtils::asyncMsgBoxCritical(mainWnd, MainWindow::tr("Error"), text); - break; - } -} - -} // namespace Internal - MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) : QMainWindow(parent), m_guiApp(guiApp), - m_ui(new Ui_MainWindow), - m_taskMgr(new TaskManager(this)) + m_ui(new Ui_MainWindow) { m_ui->setupUi(this); m_ui->widget_ModelTree->registerGuiApplication(guiApp); @@ -221,168 +69,62 @@ MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) m_ui->widget_Properties->setRowHeightFactor(1.4); m_ui->widget_Properties->clear(); - m_ui->btn_PreviousGuiDocument->setDefaultAction(m_ui->actionPreviousDoc); - m_ui->btn_NextGuiDocument->setDefaultAction(m_ui->actionNextDoc); - m_ui->btn_CloseGuiDocument->setDefaultAction(m_ui->actionCloseDoc); - - m_ui->actionAboutMayo->setText(tr("About %1").arg(QApplication::applicationName())); - m_ui->actionImport->setIcon(mayoTheme()->icon(Theme::Icon::Import)); - m_ui->actionExportSelectedItems->setIcon(mayoTheme()->icon(Theme::Icon::Export)); - m_ui->actionZoomIn->setIcon(mayoTheme()->icon(Theme::Icon::ZoomIn)); - m_ui->actionZoomOut->setIcon(mayoTheme()->icon(Theme::Icon::ZoomOut)); - m_ui->actionPreviousDoc->setIcon(mayoTheme()->icon(Theme::Icon::Back)); - m_ui->actionNextDoc->setIcon(mayoTheme()->icon(Theme::Icon::Next)); - m_ui->actionCloseDoc->setIcon(mayoTheme()->icon(Theme::Icon::Cross)); - m_ui->actionSaveImageView->setIcon(mayoTheme()->icon(Theme::Icon::Camera)); - m_ui->actionToggleLeftSidebar->setIcon(mayoTheme()->icon(Theme::Icon::LeftSidebar)); - m_ui->btn_CloseLeftSideBar->setIcon(mayoTheme()->icon(Theme::Icon::BackSquare)); - - m_ui->actionToggleLeftSidebar->setChecked(m_ui->widget_Left->isVisible()); - m_ui->actionToggleFullscreen->setChecked(this->isFullScreen()); - m_ui->actionToggleOriginTrihedron->setChecked(false); - m_ui->actionTogglePerformanceStats->setChecked(false); - mayoTheme()->setupHeaderComboBox(m_ui->combo_LeftContents); mayoTheme()->setupHeaderComboBox(m_ui->combo_GuiDocuments); - auto sigComboIndexChanged = qOverload(&QComboBox::currentIndexChanged); + m_appContext = new AppContext(this); + this->createCommands(); + this->createMenus(); + + m_ui->btn_PreviousGuiDocument->setDefaultAction(this->getCommandAction("previous-doc")); + m_ui->btn_NextGuiDocument->setDefaultAction(this->getCommandAction("next-doc")); + m_ui->btn_CloseGuiDocument->setDefaultAction(this->getCommandAction("close-doc")); + m_ui->btn_CloseLeftSideBar->setDefaultAction(this->getCommandAction("toggle-left-sidebar")); + // "HomeFiles" actions QObject::connect( m_ui->widget_HomeFiles, &WidgetHomeFiles::newDocumentRequested, - m_ui->actionNewDoc, &QAction::trigger); + this->getCommand("new-doc"), &Command::execute + ); QObject::connect( m_ui->widget_HomeFiles, &WidgetHomeFiles::openDocumentsRequested, - m_ui->actionOpen, &QAction::trigger); + this->getCommand("open-docs"), &Command::execute + ); QObject::connect( m_ui->widget_HomeFiles, &WidgetHomeFiles::recentFileOpenRequested, - this, [=](const FilePath& fp) { this->openDocument(fp); }); - // "File" actions - QObject::connect( - m_ui->actionNewDoc, &QAction::triggered, - this, &MainWindow::newDocument); - QObject::connect( - m_ui->actionOpen, &QAction::triggered, - this, &MainWindow::openDocuments); - QObject::connect( - m_ui->actionImport, &QAction::triggered, - this, &MainWindow::importInCurrentDoc); - QObject::connect( - m_ui->actionExportSelectedItems, &QAction::triggered, - this, &MainWindow::exportSelectedItems); - QObject::connect( - m_ui->actionCloseDoc, &QAction::triggered, - this, &MainWindow::closeCurrentDocument); - QObject::connect( - m_ui->actionCloseAllDocuments, &QAction::triggered, - this, &MainWindow::closeAllDocuments); - QObject::connect( - m_ui->actionCloseAllExcept, &QAction::triggered, - this, &MainWindow::closeAllDocumentsExceptCurrent); - QObject::connect( - m_ui->actionQuit, &QAction::triggered, - this, &MainWindow::quitApp); - QObject::connect( - m_ui->menu_File, &QMenu::aboutToShow, - this, &MainWindow::createMenuRecentFiles); - QObject::connect( - m_ui->menu_Display, &QMenu::aboutToShow, - this, &MainWindow::createMenuDisplayMode); - // "Display" actions - { - auto group = new QActionGroup(m_ui->menu_Projection); - group->setExclusive(true); - group->addAction(m_ui->actionProjectionOrthographic); - group->addAction(m_ui->actionProjectionPerspective); - } - QObject::connect(m_ui->menu_Projection, &QMenu::triggered, this, [=](QAction* action){ - if (this->currentWidgetGuiDocument()) { - const GuiDocument* guiDoc = this->currentWidgetGuiDocument()->guiDocument(); - guiDoc->v3dView()->Camera()->SetProjectionType( - action == m_ui->actionProjectionOrthographic ? - Graphic3d_Camera::Projection_Orthographic : - Graphic3d_Camera::Projection_Perspective); - guiDoc->v3dView()->Update(); - } - }); - QObject::connect( - m_ui->actionToggleOriginTrihedron, &QAction::toggled, - this, &MainWindow::toggleCurrentDocOriginTrihedron); - QObject::connect( - m_ui->actionTogglePerformanceStats, &QAction::toggled, - this, &MainWindow::toggleCurrentDocPerformanceStats); - QObject::connect( - m_ui->actionZoomIn, &QAction::triggered, - this, &MainWindow::zoomInCurrentDoc); - QObject::connect( - m_ui->actionZoomOut, &QAction::triggered, - this, &MainWindow::zoomOutCurrentDoc); - // "Tools" actions - QObject::connect( - m_ui->actionSaveImageView, &QAction::triggered, - this, &MainWindow::saveImageView); - QObject::connect( - m_ui->actionInspectXDE, &QAction::triggered, - this, &MainWindow::inspectXde); - QObject::connect( - m_ui->actionOptions, &QAction::triggered, - this, &MainWindow::editOptions); - // "Help" actions - QObject::connect( - m_ui->actionReportBug, &QAction::triggered, - this, &MainWindow::reportbug); - QObject::connect( - m_ui->actionAboutMayo, &QAction::triggered, - this, &MainWindow::aboutMayo); + this, &MainWindow::openDocument + ); // "Window" actions and navigation in documents QObject::connect( - m_ui->actionToggleFullscreen, &QAction::toggled, - this, &MainWindow::toggleFullscreen); - QObject::connect( - m_ui->actionToggleLeftSidebar, &QAction::toggled, - this, &MainWindow::toggleLeftSidebar); - QObject::connect(m_ui->actionPreviousDoc, &QAction::triggered, this, [=]{ - this->setCurrentDocumentIndex(this->currentDocumentIndex() - 1); - }); - QObject::connect(m_ui->actionNextDoc, &QAction::triggered, this, [=]{ - this->setCurrentDocumentIndex(this->currentDocumentIndex() + 1); - }); - QObject::connect( - m_ui->combo_GuiDocuments, sigComboIndexChanged, - this, &MainWindow::currentDocumentIndexChanged); - QObject::connect( - this, &MainWindow::currentDocumentIndexChanged, - this, &MainWindow::onCurrentDocumentIndexChanged); + m_ui->combo_GuiDocuments, qOverload(&QComboBox::currentIndexChanged), + this, &MainWindow::onCurrentDocumentIndexChanged + ); QObject::connect( m_ui->widget_FileSystem, &WidgetFileSystem::locationActivated, - this, &MainWindow::onWidgetFileSystemLocationActivated); - // Left header bar of controls - QObject::connect( - m_ui->btn_CloseLeftSideBar, &QAbstractButton::clicked, - this, &MainWindow::toggleLeftSidebar); + this, &MainWindow::onWidgetFileSystemLocationActivated + ); // ... QObject::connect( - m_ui->combo_LeftContents, sigComboIndexChanged, - this, &MainWindow::onLeftContentsPageChanged); - QObject::connect( - guiApp, &GuiApplication::guiDocumentAdded, - this, &MainWindow::onGuiDocumentAdded); - QObject::connect( - guiApp->selectionModel(), &ApplicationItemSelectionModel::changed, - this, &MainWindow::onApplicationItemSelectionChanged); + m_ui->combo_LeftContents, qOverload(&QComboBox::currentIndexChanged), + this, &MainWindow::onLeftContentsPageChanged + ); QObject::connect( m_ui->listView_OpenedDocuments, &QListView::clicked, - this, [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); }); - QObject::connect( - AppModule::get(), &AppModule::message, - this, [=](Messenger::MessageType msgType, const QString& text) { - Internal::handleMessage(msgType, text, this); + this, [=](const QModelIndex& index) { this->setCurrentDocumentIndex(index.row()); } + ); + guiApp->application()->signalDocumentFilePathChanged.connectSlot([=](const DocumentPtr& doc, const FilePath& fp) { + if (this->currentWidgetGuiDocument()->documentIdentifier() == doc->identifier()) + m_ui->widget_FileSystem->setLocation(filepathTo(fp)); }); + AppModule::get()->signalMessage.connectSlot(&MainWindow::onMessage, this); + guiApp->signalGuiDocumentAdded.connectSlot(&MainWindow::onGuiDocumentAdded, this); + guiApp->selectionModel()->signalChanged.connectSlot(&MainWindow::onApplicationItemSelectionChanged, this); // Creation of annex objects { // Opened documents GUI auto listViewBtns = new ItemViewButtons(m_ui->listView_OpenedDocuments, this); - listViewBtns->addButton( - 1, mayoTheme()->icon(Theme::Icon::Cross), m_ui->actionCloseDoc->toolTip()); + auto actionCloseDoc = this->getCommandAction("close-doc"); + listViewBtns->addButton(1, actionCloseDoc->icon(), actionCloseDoc->toolTip()); listViewBtns->setButtonDetection(1, -1, QVariant()); listViewBtns->setButtonDisplayColumn(1, 0); listViewBtns->setButtonDisplayModes(1, ItemViewButtons::DisplayOnDetection); @@ -391,12 +133,15 @@ MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) listViewBtns->setButtonIconSize(1, QSize(iconSize * 0.66, iconSize * 0.66)); listViewBtns->installDefaultItemDelegate(); QObject::connect(listViewBtns, &ItemViewButtons::buttonClicked, this, [=](int btnId, QModelIndex index) { - if (btnId == 1) - this->closeDocument(index.row()); + if (btnId == 1) { + auto widgetDoc = this->widgetGuiDocument(index.row()); + if (widgetDoc) + FileCommandTools::closeDocument(m_appContext, widgetDoc->documentIdentifier()); + } }); } - new DialogTaskManager(m_taskMgr, this); + new DialogTaskManager(&m_taskMgr, this); // BEWARE MainWindow::onGuiDocumentAdded() must be called before // MainWindow::onCurrentDocumentIndexChanged() @@ -404,8 +149,7 @@ MainWindow::MainWindow(GuiApplication* guiApp, QWidget *parent) m_ui->combo_GuiDocuments->setModel(guiDocModel); m_ui->listView_OpenedDocuments->setModel(guiDocModel); - // Finialize setup - this->setAcceptDrops(true); + // Finalize setup m_ui->widget_LeftHeader->installEventFilter(this); m_ui->widget_ControlGuiDocuments->installEventFilter(this); m_ui->stack_GuiDocuments->installEventFilter(this); @@ -450,25 +194,6 @@ bool MainWindow::eventFilter(QObject* watched, QEvent* event) return false; } -void MainWindow::dragEnterEvent(QDragEnterEvent* event) -{ - if (event->mimeData()->hasUrls()) - event->acceptProposedAction(); -} - -void MainWindow::dropEvent(QDropEvent* event) -{ - const QList listUrl = event->mimeData()->urls(); - std::vector listFilePath; - for (const QUrl& url : listUrl) { - if (url.isLocalFile()) - listFilePath.push_back(filepathFrom(url.toLocalFile())); - } - - event->acceptProposedAction(); - this->openDocumentsFromList(listFilePath); -} - void MainWindow::showEvent(QShowEvent* event) { QMainWindow::showEvent(event); @@ -476,202 +201,112 @@ void MainWindow::showEvent(QShowEvent* event) constexpr Qt::FindChildOption findMode = Qt::FindDirectChildrenOnly; auto winProgress = this->findChild(QString(), findMode); if (!winProgress) - winProgress = new WinTaskbarGlobalProgress(m_taskMgr, this); + winProgress = new WinTaskbarGlobalProgress(&m_taskMgr, this); winProgress->setWindow(this->windowHandle()); #endif } -void MainWindow::newDocument() -{ - static unsigned docSequenceId = 0; - auto docPtr = m_guiApp->application()->newDocument(Document::Format::Binary); - docPtr->setName(to_stdString(tr("Anonymous%1").arg(++docSequenceId))); -} - -void MainWindow::openDocuments() -{ - const auto resFileNames = Internal::OpenFileNames::get(this); - if (!resFileNames.listFilepath.empty()) - this->openDocumentsFromList(resFileNames.listFilepath); -} - -void MainWindow::importInCurrentDoc() -{ - auto widgetGuiDoc = this->currentWidgetGuiDocument(); - if (!widgetGuiDoc) - return; - - const auto resFileNames = Internal::OpenFileNames::get(this); - if (resFileNames.listFilepath.empty()) - return; - - auto appModule = AppModule::get(); - const TaskId taskId = m_taskMgr->newTask([=](TaskProgress* progress) { - QElapsedTimer chrono; - chrono.start(); - - const bool okImport = appModule->ioSystem()->importInDocument() - .targetDocument(widgetGuiDoc->guiDocument()->document()) - .withFilepaths(resFileNames.listFilepath) - .withParametersProvider(appModule) - .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { - appModule->computeBRepMesh(labelEntity, progress); - }) - .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) - .withEntityPostProcessInfoProgress(20, textIdTr("Mesh BRep shapes")) - .withMessenger(appModule) - .withTaskProgress(progress) - .execute(); - if (okImport) - appModule->emitInfo(fmt::format(textIdTr("Import time: {}ms"), chrono.elapsed())); - }); - const QString taskTitle = - resFileNames.listFilepath.size() > 1 ? - tr("Import") : - filepathTo(resFileNames.listFilepath.front().stem()); - m_taskMgr->setTitle(taskId, to_stdString(taskTitle)); - m_taskMgr->run(taskId); - for (const FilePath& fp : resFileNames.listFilepath) - appModule->prependRecentFile(fp); -} - -void MainWindow::exportSelectedItems() -{ - auto appModule = AppModule::get(); - QStringList listWriterFileFilter; - for (IO::Format format : appModule->ioSystem()->writerFormats()) - listWriterFileFilter.append(Internal::fileFilter(format)); - - auto lastSettings = Internal::ImportExportSettings::load(); - const QString strFilepath = - QFileDialog::getSaveFileName( - this, - tr("Select Output File"), - filepathTo(lastSettings.openDir), - listWriterFileFilter.join(QLatin1String(";;")), - &lastSettings.selectedFilter); - if (strFilepath.isEmpty()) - return; - - lastSettings.openDir = filepathFrom(strFilepath); - const IO::Format format = Internal::formatFromFilter(lastSettings.selectedFilter); - const TaskId taskId = m_taskMgr->newTask([=](TaskProgress* progress) { - QElapsedTimer chrono; - chrono.start(); - const bool okExport = - appModule->ioSystem()->exportApplicationItems() - .targetFile(filepathFrom(strFilepath)) - .targetFormat(format) - .withItems(m_guiApp->selectionModel()->selectedItems()) - .withParameters(appModule->findWriterParameters(format)) - .withMessenger(appModule) - .withTaskProgress(progress) - .execute(); - if (okExport) - appModule->emitInfo(fmt::format(textIdTr("Export time: {}ms"), chrono.elapsed())); - }); - m_taskMgr->setTitle(taskId, to_stdString(QFileInfo(strFilepath).fileName())); - m_taskMgr->run(taskId); - Internal::ImportExportSettings::save(lastSettings); -} - -void MainWindow::quitApp() -{ - QApplication::quit(); -} +void MainWindow::createCommands() +{ + // "File" commands + this->addCommand("new-doc"); + this->addCommand("open-docs"); + this->addCommand("recent-files", m_ui->menu_File); + this->addCommand("import"); + this->addCommand("export"); + this->addCommand("close-doc"); + this->addCommand("close-all-docs"); + this->addCommand("close-all-docs-except-current"); + this->addCommand("quit"); + + // "Display" commands + this->addCommand("change-projection"); + this->addCommand("change-display-mode", m_ui->menu_Display); + this->addCommand("toggle-origin-trihedron"); + this->addCommand("toggle-performance-stats"); + this->addCommand("current-doc-zoom-in"); + this->addCommand("current-doc-zoom-out"); + this->addCommand("current-doc-turn-view-ccw"); + this->addCommand("current-doc-turn-view-cw"); + + // "Tools" commands + this->addCommand("save-view-image"); + this->addCommand("inspect-xde"); + this->addCommand("edit-options"); + + // "Window" commands + this->addCommand("toggle-left-sidebar"); + this->addCommand("toggle-fullscreen"); + this->addCommand("previous-doc"); + this->addCommand("next-doc"); + + // "Help" commands + this->addCommand("report-bug"); + this->addCommand("about"); +} + +void MainWindow::createMenus() +{ + // Helper function to retrieve the QAction associated to the name of a command + auto fnGetAction = [=](std::string_view commandName) { + return this->getCommandAction(commandName); + }; -void MainWindow::toggleCurrentDocOriginTrihedron() -{ - WidgetGuiDocument* widget = this->currentWidgetGuiDocument(); - if (widget) { - widget->guiDocument()->toggleOriginTrihedronVisibility(); - widget->guiDocument()->graphicsScene()->redraw(); + { // File + auto menu = m_ui->menu_File; + menu->addAction(fnGetAction("new-doc")); + menu->addAction(fnGetAction("open-docs")); + menu->addAction(fnGetAction("recent-files")); + menu->addSeparator(); + menu->addAction(fnGetAction("import")); + menu->addAction(fnGetAction("export")); + menu->addSeparator(); + menu->addAction(fnGetAction("close-doc")); + menu->addAction(fnGetAction("close-all-docs-except-current")); + menu->addAction(fnGetAction("close-all-docs")); + menu->addSeparator(); + menu->addAction(fnGetAction("quit")); } -} -void MainWindow::toggleCurrentDocPerformanceStats() -{ - WidgetGuiDocument* widget = this->currentWidgetGuiDocument(); - GuiDocument* guiDoc = widget ? widget->guiDocument() : nullptr; - if (guiDoc) { - CppUtils::toggle(guiDoc->v3dView()->ChangeRenderingParams().ToShowStats); - guiDoc->graphicsScene()->redraw(); + { // Display + auto menu = m_ui->menu_Display; + menu->addAction(fnGetAction("change-projection")); + menu->addAction(fnGetAction("change-display-mode")); + menu->addAction(fnGetAction("toggle-origin-trihedron")); + menu->addAction(fnGetAction("toggle-performance-stats")); + menu->addSeparator(); + menu->addAction(fnGetAction("current-doc-zoom-in")); + menu->addAction(fnGetAction("current-doc-zoom-out")); + menu->addAction(fnGetAction("current-doc-turn-view-ccw")); + menu->addAction(fnGetAction("current-doc-turn-view-cw")); } -} - -void MainWindow::zoomInCurrentDoc() -{ - this->currentWidgetGuiDocument()->controller()->zoomIn(); -} - -void MainWindow::zoomOutCurrentDoc() -{ - this->currentWidgetGuiDocument()->controller()->zoomOut(); -} - -void MainWindow::editOptions() -{ - auto dlg = new DialogOptions(AppModule::get()->settings(), this); - WidgetsUtils::asyncDialogExec(dlg); -} -void MainWindow::saveImageView() -{ - auto widgetGuiDoc = this->currentWidgetGuiDocument(); - auto dlg = new DialogSaveImageView(widgetGuiDoc->guiDocument()->v3dView()); - WidgetsUtils::asyncDialogExec(dlg); -} - -void MainWindow::inspectXde() -{ - const Span spanAppItem = m_guiApp->selectionModel()->selectedItems(); - DocumentPtr xcafDoc; - for (const ApplicationItem& appItem : spanAppItem) { - if (appItem.document()->isXCafDocument()) { - xcafDoc = appItem.document(); - break; - } + { // Tools + auto menu = m_ui->menu_Tools; + menu->addAction(fnGetAction("save-view-image")); + menu->addAction(fnGetAction("inspect-xde")); + menu->addSeparator(); + menu->addAction(fnGetAction("edit-options")); } - if (!xcafDoc.IsNull()) { - auto dlg = new DialogInspectXde(this); - dlg->load(xcafDoc); - WidgetsUtils::asyncDialogExec(dlg); + { // Window + auto menu = m_ui->menu_Window; + menu->addAction(fnGetAction("toggle-left-sidebar")); + menu->addAction(fnGetAction("toggle-fullscreen")); + menu->addSeparator(); + menu->addAction(fnGetAction("previous-doc")); + menu->addAction(fnGetAction("next-doc")); } -} -void MainWindow::toggleFullscreen() -{ - if (this->isFullScreen()) { - if (m_previousWindowState.testFlag(Qt::WindowMaximized)) - this->showMaximized(); - else - this->showNormal(); - } - else { - m_previousWindowState = this->windowState(); - this->showFullScreen(); + { // Help + auto menu = m_ui->menu_Help; + menu->addAction(fnGetAction("report-bug")); + menu->addSeparator(); + menu->addAction(fnGetAction("about")); } } -void MainWindow::toggleLeftSidebar() -{ - const bool isVisible = m_ui->widget_Left->isVisible(); - m_ui->widget_Left->setVisible(!isVisible); -} - -void MainWindow::aboutMayo() -{ - auto dlg = new DialogAbout(this); - WidgetsUtils::asyncDialogExec(dlg); -} - -void MainWindow::reportbug() -{ - QDesktopServices::openUrl(QUrl(QStringLiteral("https://github.com/fougue/mayo/issues"))); -} - void MainWindow::onApplicationItemSelectionChanged() { WidgetModelTree* uiModelTree = m_ui->widget_ModelTree; @@ -691,9 +326,7 @@ void MainWindow::onApplicationItemSelectionChanged() auto dataProps = AppModule::get()->properties(docTreeNode); if (dataProps) { uiProps->editProperties(dataProps.get(), uiProps->addGroup(tr("Data"))); - QObject::connect(dataProps.get(), &PropertyGroupSignals::propertyChanged, this, [=]{ - uiModelTree->refreshItemText(appItem); - }); + dataProps->signalPropertyChanged.connectSlot([=]{ uiModelTree->refreshItemText(appItem); }); m_ptrCurrentNodeDataProperties = std::move(dataProps); } @@ -707,9 +340,7 @@ void MainWindow::onApplicationItemSelectionChanged() auto gfxProps = commonGfxDriver->properties(vecGfxObject); if (gfxProps) { uiProps->editProperties(gfxProps.get(), uiProps->addGroup(tr("Graphics"))); - QObject::connect(gfxProps.get(), &PropertyGroupSignals::propertyChanged, this, [=]{ - guiDoc->graphicsScene()->redraw(); - }); + gfxProps->signalPropertyChanged.connectSlot([=]{ guiDoc->graphicsScene()->redraw(); }); m_ptrCurrentNodeGraphicsProperties = std::move(gfxProps); } } @@ -735,7 +366,7 @@ void MainWindow::onOperationFinished(bool ok, const QString &msg) if (ok) WidgetMessageIndicator::showInfo(msg, this); else - WidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), msg); + QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), msg); } void MainWindow::onGuiDocumentAdded(GuiDocument* guiDoc) @@ -769,6 +400,7 @@ void MainWindow::onGuiDocumentAdded(GuiDocument* guiDoc) auto appModule = AppModule::get(); auto appProps = appModule->properties(); auto widget = new WidgetGuiDocument(guiDoc); + guiDoc->setDevicePixelRatio(widget->devicePixelRatioF()); auto widgetCtrl = widget->controller(); widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); widgetCtrl->setNavigationStyle(appProps->navigationStyle); @@ -777,7 +409,7 @@ void MainWindow::onGuiDocumentAdded(GuiDocument* guiDoc) gfxScene->redraw(); } - QObject::connect(appModule->settings(), &Settings::changed, this, [=](Property* setting) { + appModule->settings()->signalChanged.connectSlot([=](const Property* setting) { if (setting == &appProps->instantZoomFactor) widgetCtrl->setInstantZoomFactor(appProps->instantZoomFactor); else if (setting == &appProps->navigationStyle) @@ -787,15 +419,16 @@ void MainWindow::onGuiDocumentAdded(GuiDocument* guiDoc) // 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()); + widgetCtrl->signalMouseMoved.connectSlot([=](int xPos, int yPos) { + const double dpRatio = this->devicePixelRatioF(); + gfxScene->highlightAt(xPos * dpRatio, yPos * dpRatio, guiDoc->v3dView()); widget->view()->redraw(); auto selector = gfxScene->mainSelector(); - selector->Pick(pos2d.x(), pos2d.y(), guiDoc->v3dView()); + selector->Pick(xPos, yPos, guiDoc->v3dView()); const gp_Pnt pos3d = selector->NbPicked() > 0 ? selector->PickedPoint(1) : - GraphicsUtils::V3dView_to3dPosition(guiDoc->v3dView(), pos2d.x(), pos2d.y()); + GraphicsUtils::V3dView_to3dPosition(guiDoc->v3dView(), xPos, yPos); m_ui->label_ValuePosX->setText(QString::number(pos3d.X(), 'f', 3)); m_ui->label_ValuePosY->setText(QString::number(pos3d.Y(), 'f', 3)); m_ui->label_ValuePosZ->setText(QString::number(pos3d.Z(), 'f', 3)); @@ -840,145 +473,36 @@ void MainWindow::onCurrentDocumentIndexChanged(int idx) this->updateControlsActivation(); - auto fnFilepathQuoted = [](const QString& filepath) { - for (QChar c : filepath) { - if (c.isSpace()) - return "\"" + filepath + "\""; - } - return filepath; - }; const DocumentPtr docPtr = m_guiApp->application()->findDocumentByIndex(idx); - const QString docName = to_QString(docPtr ? docPtr->name() : std::string{}); - const QString textActionClose = - docPtr ? - tr("Close %1").arg(fnFilepathQuoted(docName)) : - tr("Close"); - const QString textActionCloseAllExcept = - docPtr ? - tr("Close all except %1").arg(fnFilepathQuoted(docName)) : - tr("Close all except current"); const FilePath docFilePath = docPtr ? docPtr->filePath() : FilePath(); - m_ui->actionCloseDoc->setText(textActionClose); - m_ui->actionCloseAllExcept->setText(textActionCloseAllExcept); m_ui->widget_FileSystem->setLocation(filepathTo(docFilePath)); - - if (this->currentWidgetGuiDocument()) { - const GuiDocument* guiDoc = this->currentWidgetGuiDocument()->guiDocument(); - // Sync action with current visibility status of origin trihedron - { - QSignalBlocker sigBlk(m_ui->actionToggleOriginTrihedron); Q_UNUSED(sigBlk); - m_ui->actionToggleOriginTrihedron->setChecked(guiDoc->isOriginTrihedronVisible()); - } - // Sync action with current visibility status of rendering performance stats - { - QSignalBlocker sigBlk(m_ui->actionTogglePerformanceStats); Q_UNUSED(sigBlk); - m_ui->actionTogglePerformanceStats->setChecked(guiDoc->v3dView()->ChangeRenderingParams().ToShowStats); - } - // Sync menu with current projection type - { - const Graphic3d_Camera::Projection viewProjectionType = - guiDoc->v3dView()->Camera()->ProjectionType(); - Q_ASSERT(viewProjectionType == Graphic3d_Camera::Projection_Perspective - || viewProjectionType == Graphic3d_Camera::Projection_Orthographic); - QAction* actionProjection = - viewProjectionType == Graphic3d_Camera::Projection_Perspective ? - m_ui->actionProjectionPerspective : - m_ui->actionProjectionOrthographic; - QSignalBlocker sigBlk(m_ui->menu_Projection); Q_UNUSED(sigBlk); - actionProjection->setChecked(true); - } - } - else { - m_ui->actionToggleOriginTrihedron->setChecked(false); - m_ui->actionTogglePerformanceStats->setChecked(false); - } - } - -void MainWindow::closeCurrentDocument() -{ - this->closeDocument(this->currentDocumentIndex()); -} - -void MainWindow::closeDocument(WidgetGuiDocument* widget) -{ - if (widget) { - const DocumentPtr& doc = widget->guiDocument()->document(); - m_ui->stack_GuiDocuments->removeWidget(widget); - widget->deleteLater(); - m_guiApp->application()->closeDocument(doc); - this->updateControlsActivation(); - } -} - -void MainWindow::closeDocument(int docIndex) -{ - if (0 <= docIndex && docIndex < m_ui->stack_GuiDocuments->count()) - this->closeDocument(this->widgetGuiDocument(docIndex)); } -void MainWindow::closeAllDocumentsExceptCurrent() +void MainWindow::onMessage(Messenger::MessageType msgType, const QString& text) { - WidgetGuiDocument* current = this->currentWidgetGuiDocument(); - std::vector vecWidget; - for (int i = 0; i < m_ui->stack_GuiDocuments->count(); ++i) - vecWidget.push_back(this->widgetGuiDocument(i)); - - for (WidgetGuiDocument* widget : vecWidget) { - if (widget != current) - this->closeDocument(widget); + switch (msgType) { + case Messenger::MessageType::Trace: + break; + case Messenger::MessageType::Info: + WidgetMessageIndicator::showInfo(text, this); + break; + case Messenger::MessageType::Warning: + QtWidgetsUtils::asyncMsgBoxWarning(this, tr("Warning"), text); + break; + case Messenger::MessageType::Error: + QtWidgetsUtils::asyncMsgBoxCritical(this, tr("Error"), text); + break; } } -void MainWindow::closeAllDocuments() -{ - while (m_ui->stack_GuiDocuments->count() > 0) - this->closeCurrentDocument(); -} - void MainWindow::openDocument(const FilePath& fp) { - this->openDocumentsFromList(Span(&fp, 1)); + FileCommandTools::openDocument(m_appContext, fp); } void MainWindow::openDocumentsFromList(Span listFilePath) { - auto app = m_guiApp->application(); - auto appModule = AppModule::get(); - for (const FilePath& fp : listFilePath) { - DocumentPtr docPtr = app->findDocumentByLocation(fp); - if (docPtr.IsNull()) { - docPtr = app->newDocument(); - const TaskId taskId = m_taskMgr->newTask([=](TaskProgress* progress) { - QElapsedTimer chrono; - chrono.start(); - docPtr->setName(fp.stem().u8string()); - docPtr->setFilePath(fp); - - const bool okImport = - appModule->ioSystem()->importInDocument() - .targetDocument(docPtr) - .withFilepath(fp) - .withParametersProvider(appModule) - .withEntityPostProcess([=](TDF_Label labelEntity, TaskProgress* progress) { - appModule->computeBRepMesh(labelEntity, progress); - }) - .withEntityPostProcessRequiredIf(&IO::formatProvidesBRep) - .withEntityPostProcessInfoProgress(20, textIdTr("Mesh BRep shapes")) - .withMessenger(appModule) - .withTaskProgress(progress) - .execute(); - if (okImport) - appModule->emitInfo(fmt::format(textIdTr("Import time: {}ms"), chrono.elapsed())); - }); - m_taskMgr->setTitle(taskId, fp.stem().u8string()); - m_taskMgr->run(taskId); - AppModule::get()->prependRecentFile(fp); - } - else { - if (listFilePath.size() == 1) - this->setCurrentDocumentIndex(app->findIndexOfDocument(docPtr)); - } - } + FileCommandTools::openDocumentsFromList(m_appContext, listFilePath); } void MainWindow::updateControlsActivation() @@ -990,33 +514,11 @@ void MainWindow::updateControlsActivation() if (currMainPage != newMainPage) m_ui->stack_Main->setCurrentWidget(newMainPage); - m_ui->actionImport->setEnabled(!appDocumentsEmpty); - m_ui->menu_Projection->setEnabled(!appDocumentsEmpty); - m_ui->actionProjectionOrthographic->setEnabled(!appDocumentsEmpty); - m_ui->actionProjectionPerspective->setEnabled(!appDocumentsEmpty); - m_ui->actionDisplayMode->setEnabled(!appDocumentsEmpty); - m_ui->actionToggleOriginTrihedron->setEnabled(!appDocumentsEmpty); - m_ui->actionTogglePerformanceStats->setEnabled(!appDocumentsEmpty); - m_ui->actionZoomIn->setEnabled(!appDocumentsEmpty); - m_ui->actionZoomOut->setEnabled(!appDocumentsEmpty); - m_ui->actionSaveImageView->setEnabled(!appDocumentsEmpty); - m_ui->actionCloseDoc->setEnabled(!appDocumentsEmpty); - m_ui->actionCloseAllDocuments->setEnabled(!appDocumentsEmpty); - m_ui->actionCloseAllExcept->setEnabled(!appDocumentsEmpty); - const int currentDocIndex = this->currentDocumentIndex(); - m_ui->actionPreviousDoc->setEnabled(!appDocumentsEmpty && currentDocIndex > 0); - m_ui->actionNextDoc->setEnabled(!appDocumentsEmpty && currentDocIndex < appDocumentsCount - 1); - m_ui->actionExportSelectedItems->setEnabled(!appDocumentsEmpty); - m_ui->actionToggleLeftSidebar->setEnabled(newMainPage != m_ui->page_MainHome); - m_ui->combo_GuiDocuments->setEnabled(!appDocumentsEmpty); + for (auto [name, cmd] : m_mapCommand) { + cmd->action()->setEnabled(cmd->getEnabledStatus()); + } - Span spanSelectedAppItem = m_guiApp->selectionModel()->selectedItems(); - const ApplicationItem firstAppItem = - !spanSelectedAppItem.empty() ? spanSelectedAppItem.front() : ApplicationItem(); - m_ui->actionInspectXDE->setEnabled( - spanSelectedAppItem.size() == 1 - && firstAppItem.isValid() - && firstAppItem.document()->isXCafDocument()); + m_ui->combo_GuiDocuments->setEnabled(!appDocumentsEmpty); } int MainWindow::currentDocumentIndex() const @@ -1042,7 +544,8 @@ WidgetGuiDocument* MainWindow::currentWidgetGuiDocument() const QWidget* MainWindow::findLeftHeaderPlaceHolder() const { return m_ui->widget_LeftHeader->findChild( - "LeftHeaderPlaceHolder", Qt::FindDirectChildrenOnly); + "LeftHeaderPlaceHolder", Qt::FindDirectChildrenOnly + ); } QWidget* MainWindow::recreateLeftHeaderPlaceHolder() @@ -1074,8 +577,8 @@ QMenu* MainWindow::createMenuModelTreeSettings() // Model tree user actions menu->addSeparator(); const WidgetModelTree_UserActions userActions = m_ui->widget_ModelTree->createUserActions(menu); - for (QAction* action : userActions.items) - menu->addAction(action); + for (QAction* usrAction : userActions.items) + menu->addAction(usrAction); // Sync before menu show QObject::connect(menu, &QMenu::aboutToShow, this, [=]{ @@ -1087,75 +590,16 @@ QMenu* MainWindow::createMenuModelTreeSettings() return menu; } -QMenu* MainWindow::createMenuRecentFiles() +Command* MainWindow::getCommand(std::string_view name) const { - QMenu* menu = m_ui->actionRecentFiles->menu(); - if (!menu) - menu = new QMenu(this); - - menu->clear(); - int idFile = 0; - 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); }); - } - - if (!recentFiles.empty()) { - menu->addSeparator(); - menu->addAction(tr("Clear menu"), this, [=]{ - menu->clear(); - appModule->properties()->recentFiles.setValue({}); - }); - } - - m_ui->actionRecentFiles->setMenu(menu); - return menu; + auto it = m_mapCommand.find(name); + return it != m_mapCommand.cend() ? it->second : nullptr; } -QMenu* MainWindow::createMenuDisplayMode() +QAction* MainWindow::getCommandAction(std::string_view name) const { - QMenu* menu = m_ui->actionDisplayMode->menu(); - if (!menu) { - menu = new QMenu(this); - m_ui->actionDisplayMode->setMenu(menu); - } - - menu->clear(); - - WidgetGuiDocument* widgetGuiDoc = this->currentWidgetGuiDocument(); - GuiDocument* guiDoc = widgetGuiDoc ? widgetGuiDoc->guiDocument() : nullptr; - if (!guiDoc) - return menu; - - const auto spanDrivers = m_guiApp->graphicsObjectDrivers(); - for (const GraphicsObjectDriverPtr& driver : spanDrivers) { - if (driver->displayModes().empty()) - continue; // Skip - - if (driver != spanDrivers.front()) - menu->addSeparator(); - - auto group = new QActionGroup(menu); - group->setExclusive(true); - for (const Enumeration::Item& displayMode : driver->displayModes().items()) { - auto action = new QAction(to_QString(displayMode.name.tr()), menu); - action->setCheckable(true); - action->setData(displayMode.value); - menu->addAction(action); - group->addAction(action); - if (displayMode.value == guiDoc->activeDisplayMode(driver)) - action->setChecked(true); - } - - QObject::connect(group, &QActionGroup::triggered, this, [=](QAction* action) { - guiDoc->setActiveDisplayMode(driver, action->data().toInt()); - guiDoc->graphicsScene()->redraw(); - }); - } - - return menu; + auto cmd = this->getCommand(name); + return cmd ? cmd->action() : nullptr; } } // namespace Mayo diff --git a/src/app/mainwindow.h b/src/app/mainwindow.h index 4c249162..608aa5c7 100644 --- a/src/app/mainwindow.h +++ b/src/app/mainwindow.h @@ -7,7 +7,9 @@ #pragma once #include "../base/filepath.h" +#include "../base/messenger.h" #include "../base/property.h" +#include "../base/task_manager.h" #include "../base/text_id.h" #include #include @@ -15,11 +17,14 @@ class QFileInfo; namespace Mayo { +class Command; class GuiApplication; class GuiDocument; -class TaskManager; +class IAppContext; class WidgetGuiDocument; +// Provides the root widget of the application GUI +// It creates and owns the various available commands(actions) class MainWindow : public QMainWindow { Q_OBJECT MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::MainWindow) @@ -32,39 +37,16 @@ class MainWindow : public QMainWindow { bool eventFilter(QObject* watched, QEvent* event) override; -signals: - void currentDocumentIndexChanged(int docIdx); - protected: - void dragEnterEvent(QDragEnterEvent* event) override; - void dropEvent(QDropEvent* event) override; void showEvent(QShowEvent* event) override; private: - // -- File menu - void newDocument(); - void openDocuments(); - void importInCurrentDoc(); - void exportSelectedItems(); - void closeCurrentDocument(); - void closeAllDocumentsExceptCurrent(); - void closeAllDocuments(); - void quitApp(); - // -- Display menu - void toggleCurrentDocOriginTrihedron(); - void toggleCurrentDocPerformanceStats(); - void zoomInCurrentDoc(); - void zoomOutCurrentDoc(); - // -- Tools menu - void editOptions(); - void saveImageView(); - void inspectXde(); - // -- Window menu - void toggleFullscreen(); - void toggleLeftSidebar(); - // -- Help menu - void aboutMayo(); - void reportbug(); + void createCommands(); + void createMenus(); + + Command* getCommand(std::string_view name) const; + QAction* getCommandAction(std::string_view name) const; + template CmdType* addCommand(std::string_view name, Args... p); void onApplicationItemSelectionChanged(); void onOperationFinished(bool ok, const QString& msg); @@ -72,9 +54,7 @@ class MainWindow : public QMainWindow { void onWidgetFileSystemLocationActivated(const QFileInfo& loc); void onLeftContentsPageChanged(int pageId); void onCurrentDocumentIndexChanged(int idx); - - void closeDocument(WidgetGuiDocument* widget); - void closeDocument(int docIndex); + void onMessage(Messenger::MessageType msgType, const QString& text); void updateControlsActivation(); @@ -86,15 +66,29 @@ class MainWindow : public QMainWindow { QWidget* findLeftHeaderPlaceHolder() const; QWidget* recreateLeftHeaderPlaceHolder(); QMenu* createMenuModelTreeSettings(); - QMenu* createMenuRecentFiles(); - QMenu* createMenuDisplayMode(); + friend class AppContext; + + IAppContext* m_appContext = nullptr; GuiApplication* m_guiApp = nullptr; + TaskManager m_taskMgr; class Ui_MainWindow* m_ui = nullptr; - TaskManager* m_taskMgr = nullptr; - Qt::WindowStates m_previousWindowState = Qt::WindowNoState; + std::unordered_map m_mapCommand; std::unique_ptr m_ptrCurrentNodeDataProperties; std::unique_ptr m_ptrCurrentNodeGraphicsProperties; }; + + +// -- +// -- Implementation +// -- + +template CmdType* MainWindow::addCommand(std::string_view name, Args... p) +{ + auto cmd = new CmdType(m_appContext, p...); + m_mapCommand.insert({ name, cmd }); + return cmd; +} + } // namespace Mayo diff --git a/src/app/mainwindow.ui b/src/app/mainwindow.ui index 9b5e7543..d0cf6e20 100644 --- a/src/app/mainwindow.ui +++ b/src/app/mainwindow.ui @@ -556,289 +556,40 @@ 0 0 950 - 21 + 22 &File - - - - - - - - - - - - &Help - - - - + &Tools - - - - &Window - - - - - &Display - - - Projection - - - - - - - - - - - - + - - - New - - - Ctrl+N - - - - - Import - - - - - Quit - - - - - Open - - - Ctrl+O - - - - - About Mayo - - - - - Report Bug - - - - - Options - - - - - - :/images/themes/classic/camera_32.png:/images/themes/classic/camera_32.png - - - Save View to Image - - - - - Export selected items - - - - - Inspect XDE - - - - - - :/images/previous.png:/images/previous.png - - - Previous Document - - - Previous Document - - - Alt+Left - - - - - - :/images/next.png:/images/next.png - - - Next Document - - - Next Document - - - Alt+Right - - - - - - :/images/close.png:/images/close.png - - - Close "%1" - - - Ctrl+W - - - - - true - - - Fullscreen - - - Switch Fullscreen/Normal - - - F11 - - - - - true - - - - :/images/themes/classic/left-sidebar_32.png:/images/themes/classic/left-sidebar_32.png - - - Show Left Sidebar - - - Show/Hide Left Sidebar - - - Alt+0 - - - - - Recent files - - - - - true - - - Show Origin Trihedron - - - Show/Hide Origin Trihedron - - - - - Zoom In - - - Ctrl++ - - - - - Zoom Out - - - Ctrl+- - - - - - Close all - - - Ctrl+Shift+W - - - - - Close all except "%1" - - - - - true - - - Perspective - - - - - true - - - Orthographic - - - - - Mode - - - - - true - - - Show Performance Stats - - - Show/Hide rendering performance statistics - - diff --git a/src/app/occt_window_750.cpp b/src/app/occt_window.cpp similarity index 73% rename from src/app/occt_window_750.cpp rename to src/app/occt_window.cpp index 1bf3e160..ab425ea8 100644 --- a/src/app/occt_window_750.cpp +++ b/src/app/occt_window.cpp @@ -1,6 +1,4 @@ -#include -#if OCC_VERSION_HEX >= 0x070500 -#include "occt_window_750.h" +#include "occt_window.h" // -- // -- Copy from $OCC7.5.0/samples/qt/Common/src/OcctWindow.h @@ -17,10 +15,11 @@ OcctWindow::OcctWindow ( QWidget* theWidget, const Quantity_NameOfColor theBackC myWidget( theWidget ) { SetBackground (theBackColor); - myXLeft = myWidget->rect().left(); - myYTop = myWidget->rect().top(); - myXRight = myWidget->rect().right(); - myYBottom = myWidget->rect().bottom(); + const auto scale = myWidget->devicePixelRatioF(); + myXLeft = qRound(scale * myWidget->rect().left()); + myYTop = qRound(scale * myWidget->rect().top()); + myXRight = qRound(scale * myWidget->rect().right()); + myYBottom = qRound(scale * myWidget->rect().bottom()); } // ======================================================================= @@ -87,17 +86,22 @@ void OcctWindow::Unmap() const // function : DoResize // purpose : // ======================================================================= +#if OCC_VERSION_HEX >= 0x070500 Aspect_TypeOfResize OcctWindow::DoResize() +#else +Aspect_TypeOfResize OcctWindow::DoResize() const +#endif { int aMask = 0; Aspect_TypeOfResize aMode = Aspect_TOR_UNKNOWN; + const auto scale = myWidget->devicePixelRatioF(); if ( !myWidget->isMinimized() ) { - if ( Abs ( myWidget->rect().left() - myXLeft ) > 2 ) aMask |= 1; - if ( Abs ( myWidget->rect().right() - myXRight ) > 2 ) aMask |= 2; - if ( Abs ( myWidget->rect().top() - myYTop ) > 2 ) aMask |= 4; - if ( Abs ( myWidget->rect().bottom() - myYBottom ) > 2 ) aMask |= 8; + if ( Abs ( scale * myWidget->rect().left() - myXLeft ) > 2 ) aMask |= 1; + if ( Abs ( scale * myWidget->rect().right() - myXRight ) > 2 ) aMask |= 2; + if ( Abs ( scale * myWidget->rect().top() - myYTop ) > 2 ) aMask |= 4; + if ( Abs ( scale * myWidget->rect().bottom() - myYBottom ) > 2 ) aMask |= 8; switch ( aMask ) { @@ -132,10 +136,16 @@ Aspect_TypeOfResize OcctWindow::DoResize() break; } // end switch - myXLeft = myWidget->rect().left(); - myXRight = myWidget->rect().right(); - myYTop = myWidget->rect().top(); - myYBottom = myWidget->rect().bottom(); +#if OCC_VERSION_HEX >= 0x070500 + OcctWindow* mutableThis = this; +#else + OcctWindow* mutableThis = const_cast(this); +#endif + + mutableThis->myXLeft = qRound(scale * myWidget->rect().left()); + mutableThis->myXRight = qRound(scale * myWidget->rect().right()); + mutableThis->myYTop = qRound(scale * myWidget->rect().top()); + mutableThis->myYBottom = qRound(scale * myWidget->rect().bottom()); } return aMode; @@ -158,8 +168,9 @@ Standard_Real OcctWindow::Ratio() const void OcctWindow::Size ( Standard_Integer& theWidth, Standard_Integer& theHeight ) const { QRect aRect = myWidget->rect(); - theWidth = aRect.width(); - theHeight = aRect.height(); + const auto scale = myWidget->devicePixelRatioF(); + theWidth = qRound(scale * aRect.width()); + theHeight = qRound(scale * aRect.height()); } // ======================================================================= @@ -169,10 +180,9 @@ void OcctWindow::Size ( Standard_Integer& theWidth, Standard_Integer& theHeight void OcctWindow::Position ( Standard_Integer& theX1, Standard_Integer& theY1, Standard_Integer& theX2, Standard_Integer& theY2 ) const { - theX1 = myWidget->rect().left(); - theX2 = myWidget->rect().right(); - theY1 = myWidget->rect().top(); - theY2 = myWidget->rect().bottom(); + const auto scale = myWidget->devicePixelRatioF(); + theX1 = qRound(scale * myWidget->rect().left()); + theX2 = qRound(scale * myWidget->rect().right()); + theY1 = qRound(scale * myWidget->rect().top()); + theY2 = qRound(scale * myWidget->rect().bottom()); } - -#endif // OCC >= v7.5.0 diff --git a/src/app/occt_window.h b/src/app/occt_window.h index b435183c..649d9469 100644 --- a/src/app/occt_window.h +++ b/src/app/occt_window.h @@ -1,8 +1,101 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "occt_window_740.h" -#include "occt_window_750.h" +// -- +// -- Copy from $OCC7.5.0/samples/qt/Common/src/OcctWindow.h +// -- Don't include this header, use occt_window.h instead +// -- +#ifndef OcctWindow_H +#define OcctWindow_H + +#include +#include + +#include +#include +#include + +class OcctWindow; + +/* + OcctWindow class implements Aspect_Window interface using Qt API + as a platform-independent source of window geometry information. + A similar class should be used instead of platform-specific OCCT + classes (WNT_Window, Xw_Window) in any Qt 5 application using OCCT + 3D visualization. + + With Qt 5, the requirement for a Qt-based application to rely fully + on Qt public API and stop using platform-specific APIs looks mandatory. + An example of this is changed QWidget event sequence: when a widget is + first shown on the screen, a resize event is generated before the + underlying native window is resized correctly, however the QWidget instance + already holds correct size information at that moment. The OCCT classes + acting as a source of window geometry for V3d_View class (WNT_Window, Xw_Window) + are no longer compatible with changed Qt behavior because they rely on + platform-specific API that cannot return correct window geometry information + in some cases. A reasonable solution is to provide a Qt-based implementation + of Aspect_Window interface at application level. +*/ + +class OcctWindow : public Aspect_Window +{ +public: + + //! Constructor + OcctWindow( QWidget* theWidget, const Quantity_NameOfColor theBackColor = Quantity_NOC_MATRAGRAY ); + + virtual void Destroy(); + + //! Destructor + ~OcctWindow() + { + Destroy(); + } + + //! Returns native Window handle + Aspect_Drawable NativeHandle() const override; + + //! Returns parent of native Window handle. + Aspect_Drawable NativeParentHandle() const override; + + //! Applies the resizing to the window +#if OCC_VERSION_HEX >= 0x070500 + Aspect_TypeOfResize DoResize() override; +#else + Aspect_TypeOfResize DoResize() const override; +#endif + + //! Returns True if the window is opened + //! and False if the window is closed. + Standard_Boolean IsMapped() const override; + + //! Apply the mapping change to the window + //! and returns TRUE if the window is mapped at screen. + Standard_Boolean DoMapping() const override { return Standard_True; } + + //! Opens the window . + void Map() const override; + + //! Closes the window . + void Unmap() const override; + + void Position( Standard_Integer& theX1, Standard_Integer& theY1, + Standard_Integer& theX2, Standard_Integer& theY2 ) const override; + + //! Returns The Window RATIO equal to the physical + //! WIDTH/HEIGHT dimensions. + Standard_Real Ratio() const override; + + void Size( Standard_Integer& theWidth, Standard_Integer& theHeight ) const override; + + Aspect_FBConfig NativeFBConfig() const override { return NULL; } + + DEFINE_STANDARD_RTTIEXT(OcctWindow,Aspect_Window) + +protected: + Standard_Integer myXLeft; + Standard_Integer myYTop; + Standard_Integer myXRight; + Standard_Integer myYBottom; + QWidget* myWidget; +}; + + +#endif // OcctWindow_H diff --git a/src/app/occt_window_740.cpp b/src/app/occt_window_740.cpp deleted file mode 100644 index 060d1429..00000000 --- a/src/app/occt_window_740.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include -#if OCC_VERSION_HEX <= 0x070400 -#include "occt_window_740.h" - -// -- -// -- Copy from $OCC7.4.0/samples/qt/Common/src/OcctWindow.h -// -- - -IMPLEMENT_STANDARD_RTTIEXT(OcctWindow,Aspect_Window) - -// ======================================================================= -// function : OcctWindow -// purpose : -// ======================================================================= -OcctWindow::OcctWindow ( QWidget* theWidget, const Quantity_NameOfColor theBackColor ) -: Aspect_Window(), - myWidget( theWidget ) -{ - SetBackground (theBackColor); - myXLeft = myWidget->rect().left(); - myYTop = myWidget->rect().top(); - myXRight = myWidget->rect().right(); - myYBottom = myWidget->rect().bottom(); -} - -// ======================================================================= -// function : Destroy -// purpose : -// ======================================================================= -void OcctWindow::Destroy() -{ - myWidget = NULL; -} - -// ======================================================================= -// function : NativeParentHandle -// purpose : -// ======================================================================= -Aspect_Drawable OcctWindow::NativeParentHandle() const -{ - QWidget* aParentWidget = myWidget->parentWidget(); - if ( aParentWidget != NULL ) - return (Aspect_Drawable)aParentWidget->winId(); - else - return 0; -} - -// ======================================================================= -// function : NativeHandle -// purpose : -// ======================================================================= -Aspect_Drawable OcctWindow::NativeHandle() const -{ - return (Aspect_Drawable)myWidget->winId(); -} - -// ======================================================================= -// function : IsMapped -// purpose : -// ======================================================================= -Standard_Boolean OcctWindow::IsMapped() const -{ - return !( myWidget->isMinimized() || myWidget->isHidden() ); -} - -// ======================================================================= -// function : Map -// purpose : -// ======================================================================= -void OcctWindow::Map() const -{ - myWidget->show(); - myWidget->update(); -} - -// ======================================================================= -// function : Unmap -// purpose : -// ======================================================================= -void OcctWindow::Unmap() const -{ - myWidget->hide(); - myWidget->update(); -} - -// ======================================================================= -// function : DoResize -// purpose : -// ======================================================================= -Aspect_TypeOfResize OcctWindow::DoResize() const -{ - int aMask = 0; - Aspect_TypeOfResize aMode = Aspect_TOR_UNKNOWN; - - if ( !myWidget->isMinimized() ) - { - if ( Abs ( myWidget->rect().left() - myXLeft ) > 2 ) aMask |= 1; - if ( Abs ( myWidget->rect().right() - myXRight ) > 2 ) aMask |= 2; - if ( Abs ( myWidget->rect().top() - myYTop ) > 2 ) aMask |= 4; - if ( Abs ( myWidget->rect().bottom() - myYBottom ) > 2 ) aMask |= 8; - - switch ( aMask ) - { - case 0: - aMode = Aspect_TOR_NO_BORDER; - break; - case 1: - aMode = Aspect_TOR_LEFT_BORDER; - break; - case 2: - aMode = Aspect_TOR_RIGHT_BORDER; - break; - case 4: - aMode = Aspect_TOR_TOP_BORDER; - break; - case 5: - aMode = Aspect_TOR_LEFT_AND_TOP_BORDER; - break; - case 6: - aMode = Aspect_TOR_TOP_AND_RIGHT_BORDER; - break; - case 8: - aMode = Aspect_TOR_BOTTOM_BORDER; - break; - case 9: - aMode = Aspect_TOR_BOTTOM_AND_LEFT_BORDER; - break; - case 10: - aMode = Aspect_TOR_RIGHT_AND_BOTTOM_BORDER; - break; - default: - break; - } // end switch - - *( ( Standard_Integer* )&myXLeft ) = myWidget->rect().left(); - *( ( Standard_Integer* )&myXRight ) = myWidget->rect().right(); - *( ( Standard_Integer* )&myYTop ) = myWidget->rect().top(); - *( ( Standard_Integer* )&myYBottom) = myWidget->rect().bottom(); - } - - return aMode; -} - -// ======================================================================= -// function : Ratio -// purpose : -// ======================================================================= -Standard_Real OcctWindow::Ratio() const -{ - QRect aRect = myWidget->rect(); - return Standard_Real( aRect.right() - aRect.left() ) / Standard_Real( aRect.bottom() - aRect.top() ); -} - -// ======================================================================= -// function : Size -// purpose : -// ======================================================================= -void OcctWindow::Size ( Standard_Integer& theWidth, Standard_Integer& theHeight ) const -{ - QRect aRect = myWidget->rect(); - theWidth = aRect.right(); - theHeight = aRect.bottom(); -} - -// ======================================================================= -// function : Position -// purpose : -// ======================================================================= -void OcctWindow::Position ( Standard_Integer& theX1, Standard_Integer& theY1, - Standard_Integer& theX2, Standard_Integer& theY2 ) const -{ - theX1 = myWidget->rect().left(); - theX2 = myWidget->rect().right(); - theY1 = myWidget->rect().top(); - theY2 = myWidget->rect().bottom(); -} - -#endif // OCC <= v7.4.0 diff --git a/src/app/occt_window_740.h b/src/app/occt_window_740.h deleted file mode 100644 index d7d723ca..00000000 --- a/src/app/occt_window_740.h +++ /dev/null @@ -1,99 +0,0 @@ -#include -#if OCC_VERSION_HEX <= 0x070400 -// -- -// -- Copy from $OCC7.4.0/samples/qt/Common/src/OcctWindow.h -// -- Don't include this header, use occt_window.h instead -// -- -#ifndef OcctWindow_H -#define OcctWindow_H - -#include - -#include -#include -#include - -class OcctWindow; - -/* - OcctWindow class implements Aspect_Window interface using Qt API - as a platform-independent source of window geometry information. - A similar class should be used instead of platform-specific OCCT - classes (WNT_Window, Xw_Window) in any Qt 5 application using OCCT - 3D visualization. - - With Qt 5, the requirement for a Qt-based application to rely fully - on Qt public API and stop using platform-specific APIs looks mandatory. - An example of this is changed QWidget event sequence: when a widget is - first shown on the screen, a resize event is generated before the - underlying native window is resized correctly, however the QWidget instance - already holds correct size information at that moment. The OCCT classes - acting as a source of window geometry for V3d_View class (WNT_Window, Xw_Window) - are no longer compatible with changed Qt behavior because they rely on - platform-specific API that cannot return correct window geometry information - in some cases. A reasonable solution is to provide a Qt-based implementation - of Aspect_Window interface at application level. -*/ - -class OcctWindow : public Aspect_Window -{ -public: - - //! Constructor - OcctWindow( QWidget* theWidget, const Quantity_NameOfColor theBackColor = Quantity_NOC_MATRAGRAY ); - - virtual void Destroy(); - - //! Destructor - ~OcctWindow() - { - Destroy(); - } - - //! Returns native Window handle - Aspect_Drawable NativeHandle() const override; - - //! Returns parent of native Window handle. - Aspect_Drawable NativeParentHandle() const override; - - //! Applies the resizing to the window - Aspect_TypeOfResize DoResize() const override; - - //! Returns True if the window is opened - //! and False if the window is closed. - Standard_Boolean IsMapped() const override; - - //! Apply the mapping change to the window - //! and returns TRUE if the window is mapped at screen. - Standard_Boolean DoMapping() const override { return Standard_True; } - - //! Opens the window . - void Map() const override; - - //! Closes the window . - void Unmap() const override; - - void Position( Standard_Integer& theX1, Standard_Integer& theY1, - Standard_Integer& theX2, Standard_Integer& theY2 ) const override; - - //! Returns The Window RATIO equal to the physical - //! WIDTH/HEIGHT dimensions. - Standard_Real Ratio() const override; - - void Size( Standard_Integer& theWidth, Standard_Integer& theHeight ) const override; - - Aspect_FBConfig NativeFBConfig() const override { return NULL; } - - DEFINE_STANDARD_RTTIEXT(OcctWindow,Aspect_Window) - -protected: - Standard_Integer myXLeft; - Standard_Integer myYTop; - Standard_Integer myXRight; - Standard_Integer myYBottom; - QWidget* myWidget; -}; - - -#endif // OcctWindow_H -#endif // OCC <= v7.4.0 diff --git a/src/app/occt_window_750.h b/src/app/occt_window_750.h deleted file mode 100644 index 7379b96c..00000000 --- a/src/app/occt_window_750.h +++ /dev/null @@ -1,100 +0,0 @@ -#include -#if OCC_VERSION_HEX >= 0x070500 -// -- -// -- Copy from $OCC7.5.0/samples/qt/Common/src/OcctWindow.h -// -- Don't include this header, use occt_window.h instead -// -- -#ifndef OcctWindow_H -#define OcctWindow_H - - -#include - -#include -#include -#include - -class OcctWindow; - -/* - OcctWindow class implements Aspect_Window interface using Qt API - as a platform-independent source of window geometry information. - A similar class should be used instead of platform-specific OCCT - classes (WNT_Window, Xw_Window) in any Qt 5 application using OCCT - 3D visualization. - - With Qt 5, the requirement for a Qt-based application to rely fully - on Qt public API and stop using platform-specific APIs looks mandatory. - An example of this is changed QWidget event sequence: when a widget is - first shown on the screen, a resize event is generated before the - underlying native window is resized correctly, however the QWidget instance - already holds correct size information at that moment. The OCCT classes - acting as a source of window geometry for V3d_View class (WNT_Window, Xw_Window) - are no longer compatible with changed Qt behavior because they rely on - platform-specific API that cannot return correct window geometry information - in some cases. A reasonable solution is to provide a Qt-based implementation - of Aspect_Window interface at application level. -*/ - -class OcctWindow : public Aspect_Window -{ -public: - - //! Constructor - OcctWindow( QWidget* theWidget, const Quantity_NameOfColor theBackColor = Quantity_NOC_MATRAGRAY ); - - virtual void Destroy(); - - //! Destructor - ~OcctWindow() - { - Destroy(); - } - - //! Returns native Window handle - Aspect_Drawable NativeHandle() const override; - - //! Returns parent of native Window handle. - Aspect_Drawable NativeParentHandle() const override; - - //! Applies the resizing to the window - Aspect_TypeOfResize DoResize() override; - - //! Returns True if the window is opened - //! and False if the window is closed. - Standard_Boolean IsMapped() const override; - - //! Apply the mapping change to the window - //! and returns TRUE if the window is mapped at screen. - Standard_Boolean DoMapping() const override { return Standard_True; } - - //! Opens the window . - void Map() const override; - - //! Closes the window . - void Unmap() const override; - - void Position( Standard_Integer& theX1, Standard_Integer& theY1, - Standard_Integer& theX2, Standard_Integer& theY2 ) const override; - - //! Returns The Window RATIO equal to the physical - //! WIDTH/HEIGHT dimensions. - Standard_Real Ratio() const override; - - void Size( Standard_Integer& theWidth, Standard_Integer& theHeight ) const override; - - Aspect_FBConfig NativeFBConfig() const override { return NULL; } - - DEFINE_STANDARD_RTTIEXT(OcctWindow,Aspect_Window) - -protected: - Standard_Integer myXLeft; - Standard_Integer myYTop; - Standard_Integer myXRight; - Standard_Integer myYBottom; - QWidget* myWidget; -}; - - -#endif // OcctWindow_H -#endif // OCC >= v7.5.0 diff --git a/src/app/property_editor_factory.cpp b/src/app/property_editor_factory.cpp index c0ffdc85..57577344 100644 --- a/src/app/property_editor_factory.cpp +++ b/src/app/property_editor_factory.cpp @@ -10,12 +10,12 @@ #include "../base/property_builtins.h" #include "../base/property_enumeration.h" #include "../base/unit_system.h" -#include "../gui/qtgui_utils.h" #include "app_module.h" -#include "qtcore_utils.h" #include "qstring_conv.h" #include "qstring_utils.h" -#include "widgets_utils.h" +#include "qtcore_utils.h" +#include "qtgui_utils.h" +#include "qtwidgets_utils.h" #include #include @@ -200,7 +200,7 @@ struct PropertyOccColorEditor : public InterfacePropertyEditor, public QWidget { property->setValue(QtGuiUtils::toColor(c)); this->syncWithProperty(); }); - WidgetsUtils::asyncDialogExec(dlg); + QtWidgetsUtils::asyncDialogExec(dlg); }); frameLayout->addWidget(m_labelColor); @@ -211,7 +211,7 @@ struct PropertyOccColorEditor : public InterfacePropertyEditor, public QWidget { void syncWithProperty() override { const QColor qtColor = QtGuiUtils::toQColor(m_property->value()); - m_labelColor->setPixmap(PropertyEditorFactory::colorSquarePixmap(qtColor)); + m_labelColor->setPixmap(IPropertyEditorFactory::colorSquarePixmap(qtColor)); m_labelRgb->setText(QStringUtils::text(m_property->value())); } @@ -221,7 +221,7 @@ struct PropertyOccColorEditor : public InterfacePropertyEditor, public QWidget { }; // Helper generic interface over a XYZ-value property type to get/set the coordinates with gp_XYZ -template class IProperty3dCoords { +template class IProperty3dCoords { // Expected functions // static const gp_XYZ& coords(const PROPERTY_COORDS_TYPE* prop); // static void setCoords(PROPERTY_COORDS_TYPE* prop, const gp_XYZ& coords); @@ -241,9 +241,9 @@ template<> struct IProperty3dCoords { // Generic editor of XYZ-value properties // The property type must provide a partial specialization of IProperty3dCoords -template +template struct Property3dCoordsEditor : public InterfacePropertyEditor, public QWidget { - using PropertyCoordsType = PROPERTY_XYZ_TYPE; + using PropertyCoordsType = PropertyXyzType; using IProperty3dCoordsType = IProperty3dCoords; enum class Coord { X, Y, Z }; @@ -316,7 +316,7 @@ struct PropertyQuantityEditor : public InterfacePropertyEditor, public QDoubleSp PropertyQuantityEditor(BasePropertyQuantity* property, QWidget* parentWidget) : QDoubleSpinBox(parentWidget), m_property(property) { - const UnitSystem::TranslateResult trRes = PropertyEditorFactory::unitTranslate(property); + const UnitSystem::TranslateResult trRes = IPropertyEditorFactory::unitTranslate(property); this->setSuffix(QString::fromUtf8(trRes.strUnit)); this->setDecimals(AppModule::get()->properties()->unitSystemDecimals.value()); const double rangeMin = @@ -336,7 +336,7 @@ struct PropertyQuantityEditor : public InterfacePropertyEditor, public QDoubleSp } void syncWithProperty() override { - const UnitSystem::TranslateResult trRes = PropertyEditorFactory::unitTranslate(m_property); + const UnitSystem::TranslateResult trRes = IPropertyEditorFactory::unitTranslate(m_property); this->setValue(trRes.value); } @@ -392,7 +392,7 @@ void DefaultPropertyEditorFactory::syncEditorWithProperty(QWidget* editor) const } } -UnitSystem::TranslateResult PropertyEditorFactory::unitTranslate(const BasePropertyQuantity* property) +UnitSystem::TranslateResult IPropertyEditorFactory::unitTranslate(const BasePropertyQuantity* property) { if (!property) return {}; @@ -408,7 +408,7 @@ UnitSystem::TranslateResult PropertyEditorFactory::unitTranslate(const BasePrope property->quantityUnit()); } -QPixmap PropertyEditorFactory::colorSquarePixmap(const QColor& c, int sideLen) +QPixmap IPropertyEditorFactory::colorSquarePixmap(const QColor& c, int sideLen) { QPixmap pixColor(sideLen, sideLen); pixColor.fill(c); diff --git a/src/app/property_editor_factory.h b/src/app/property_editor_factory.h index 8c4feaff..07f0bbba 100644 --- a/src/app/property_editor_factory.h +++ b/src/app/property_editor_factory.h @@ -17,9 +17,9 @@ class BasePropertyQuantity; class Property; // Provides widgets for editing Property objects within views and delegates -class PropertyEditorFactory { +class IPropertyEditorFactory { public: - virtual ~PropertyEditorFactory() = default; + virtual ~IPropertyEditorFactory() = default; virtual QWidget* createEditor(Property* property, QWidget* parentWidget) const = 0; virtual void syncEditorWithProperty(QWidget* editor) const = 0; @@ -29,7 +29,7 @@ class PropertyEditorFactory { static QPixmap colorSquarePixmap(const QColor& c, int sideLen = 16); }; -class DefaultPropertyEditorFactory : public PropertyEditorFactory { +class DefaultPropertyEditorFactory : public IPropertyEditorFactory { public: QWidget* createEditor(Property* property, QWidget* parentWidget) const override; void syncEditorWithProperty(QWidget* editor) const override; diff --git a/src/app/property_item_delegate.cpp b/src/app/property_item_delegate.cpp index 6cb63091..57f3fc78 100644 --- a/src/app/property_item_delegate.cpp +++ b/src/app/property_item_delegate.cpp @@ -9,11 +9,12 @@ #include "../base/property_builtins.h" #include "../base/settings.h" #include "../base/unit_system.h" -#include "../gui/qtgui_utils.h" #include "app_module.h" #include "filepath_conv.h" +#include "qmeta_property.h" #include "qstring_conv.h" #include "qstring_utils.h" +#include "qtgui_utils.h" #include "theme.h" #include @@ -94,7 +95,7 @@ static QString propertyValueText(const PropertyBool* prop) { } static QString propertyValueText(const PropertyInt* prop) { - return AppModule::get()->locale().toString(prop->value()); + return AppModule::get()->qtLocale().toString(prop->value()); } static QString propertyValueText(const PropertyDouble* prop) { @@ -143,7 +144,7 @@ static QString propertyValueText(const BasePropertyQuantity* prop) return toStringDHMS(propTime->quantity()); } - const UnitSystem::TranslateResult trRes = PropertyEditorFactory::unitTranslate(prop); + const UnitSystem::TranslateResult trRes = IPropertyEditorFactory::unitTranslate(prop); return PropertyItemDelegate::tr("%1%2") .arg(QStringUtils::text(trRes.value, appDefaultTextOptions())) .arg(trRes.strUnit); @@ -166,7 +167,7 @@ PropertyItemDelegate::PropertyItemDelegate(QObject* parent) m_editorFactory(new DefaultPropertyEditorFactory) {} -void PropertyItemDelegate::setPropertyEditorFactory(std::unique_ptr editorFactory) +void PropertyItemDelegate::setPropertyEditorFactory(std::unique_ptr editorFactory) { m_editorFactory = std::move(editorFactory); } @@ -198,7 +199,7 @@ void PropertyItemDelegate::paint( option.widget); const QColor color = QtGuiUtils::toQColor(propColor->value()); - const QPixmap pixColor = PropertyEditorFactory::colorSquarePixmap(color, option.rect.height()); + const QPixmap pixColor = IPropertyEditorFactory::colorSquarePixmap(color, option.rect.height()); painter->drawPixmap(option.rect.x(), option.rect.y(), pixColor); const QString strColor = propertyValueText(propColor); diff --git a/src/app/property_item_delegate.h b/src/app/property_item_delegate.h index 0af7bdaa..e47e3918 100644 --- a/src/app/property_item_delegate.h +++ b/src/app/property_item_delegate.h @@ -25,38 +25,34 @@ class PropertyItemDelegate : public QStyledItemDelegate { double rowHeightFactor() const { return m_rowHeightFactor; } void setRowHeightFactor(double v) { m_rowHeightFactor = v; } - PropertyEditorFactory* editorFactory() const { return m_editorFactory.get(); } - void setPropertyEditorFactory(std::unique_ptr editorFactory); + IPropertyEditorFactory* editorFactory() const { return m_editorFactory.get(); } + void setPropertyEditorFactory(std::unique_ptr editorFactory); struct UnitTranslation { Unit unit; const char* strUnit; // UTF8 double factor; }; - bool overridePropertyUnitTranslation( - const BasePropertyQuantity* prop, UnitTranslation unitTr); + bool overridePropertyUnitTranslation(const BasePropertyQuantity* prop, UnitTranslation unitTr); void paint( QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + const QModelIndex& index + ) const override; QString displayText(const QVariant& value, const QLocale&) const override; QWidget* createEditor( - QWidget* parent, - const QStyleOptionViewItem&, - const QModelIndex& index) const override; + QWidget* parent, const QStyleOptionViewItem&, const QModelIndex& index + ) const override; - void setModelData( - QWidget*, QAbstractItemModel*, const QModelIndex&) const override; + void setModelData(QWidget*, QAbstractItemModel*, const QModelIndex&) const override; - QSize sizeHint( - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; private: - std::unique_ptr m_editorFactory; + std::unique_ptr m_editorFactory; double m_rowHeightFactor = 1.; std::unordered_map m_mapPropUnitTr; }; diff --git a/src/app/proxy_styled_item_delegate.h b/src/app/proxy_styled_item_delegate.h index 6752d989..79635ebb 100644 --- a/src/app/proxy_styled_item_delegate.h +++ b/src/app/proxy_styled_item_delegate.h @@ -11,7 +11,7 @@ namespace Mayo { // Convenience class that simplifies dynamically overriding of QStyledItemDelegate -// QStyledItemDelegate protected functions cannot be overriden through proxy technique, this is a +// QStyledItemDelegate protected functions cannot be overridden through proxy technique, this is a // limitation that applies to : // - QStyledItemDelegate::initStyleOption() // - QStyledItemDelegate::eventFilter() @@ -27,28 +27,32 @@ class ProxyStyledItemDelegate : public QStyledItemDelegate { void paint( QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; - QSize sizeHint( - const QStyleOptionViewItem& option, - const QModelIndex& index) const override; - QString displayText( - const QVariant& value, - const QLocale& locale) const override; + const QModelIndex& index + ) const override; + + QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override; + + QString displayText(const QVariant& value, const QLocale& locale) const override; QWidget* createEditor( QWidget* parent, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; - void setEditorData( - QWidget* editor, const QModelIndex& index) const override; + const QModelIndex& index + ) const override; + + void setEditorData(QWidget* editor, const QModelIndex& index) const override; + void setModelData( QWidget* editor, QAbstractItemModel* model, - const QModelIndex& index) const override; + const QModelIndex& index + ) const override; + void updateEditorGeometry( QWidget* editor, const QStyleOptionViewItem& option, - const QModelIndex& index) const override; + const QModelIndex& index + ) const override; private: QStyledItemDelegate* m_sourceDelegate; diff --git a/src/app/qmeta_property.h b/src/app/qmeta_property.h new file mode 100644 index 00000000..a51689c9 --- /dev/null +++ b/src/app/qmeta_property.h @@ -0,0 +1,12 @@ +/**************************************************************************** +** 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.h" +#include +Q_DECLARE_METATYPE(Mayo::Property*) +Q_DECLARE_METATYPE(const Mayo::Property*) diff --git a/src/app/qstring_conv.h b/src/app/qstring_conv.h index 51718f27..2b7c5277 100644 --- a/src/app/qstring_conv.h +++ b/src/app/qstring_conv.h @@ -14,8 +14,8 @@ namespace Mayo { // General API -template -QString to_QString(const STRING_TYPE& str) { +template +QString to_QString(const StringType& str) { return string_conv(str); } diff --git a/src/app/qtcore_utils.h b/src/app/qtcore_utils.h index a185c619..c854b44a 100644 --- a/src/app/qtcore_utils.h +++ b/src/app/qtcore_utils.h @@ -11,21 +11,27 @@ #include namespace Mayo { + +// Provides a collection of tools for the QtCore module namespace QtCoreUtils { +// Convenience function over QByteArray::fromRawData() taking a QByteArray object inline QByteArray QByteArray_frowRawData(const QByteArray& bytes) { return QByteArray::fromRawData(bytes.data(), bytes.size()); } +// Convenience function over QByteArray::fromRawData() taking a std::string_view object inline QByteArray QByteArray_frowRawData(std::string_view str) { return QByteArray::fromRawData(str.data(), int(str.size())); } +// Convenience function over QByteArray::fromRawData() taking a C array of characters template QByteArray QByteArray_frowRawData(const char (&str)[N]) { return QByteArray::fromRawData(str, N); } +// Converts Mayo::CheckState -> Qt::CheckState inline Qt::CheckState toQtCheckState(Mayo::CheckState state) { switch (state) { case CheckState::Off: return Qt::Unchecked; @@ -36,6 +42,7 @@ inline Qt::CheckState toQtCheckState(Mayo::CheckState state) { return Qt::Unchecked; } +// Converts Qt::CheckState -> Mayo::CheckState inline Mayo::CheckState toCheckState(Qt::CheckState state) { switch (state) { case Qt::Unchecked: return CheckState::Off; diff --git a/src/gui/qtgui_utils.cpp b/src/app/qtgui_utils.cpp similarity index 100% rename from src/gui/qtgui_utils.cpp rename to src/app/qtgui_utils.cpp diff --git a/src/gui/qtgui_utils.h b/src/app/qtgui_utils.h similarity index 79% rename from src/gui/qtgui_utils.h rename to src/app/qtgui_utils.h index 58399bb8..0a418e4b 100644 --- a/src/gui/qtgui_utils.h +++ b/src/app/qtgui_utils.h @@ -20,22 +20,24 @@ class QScreen; #include namespace Mayo { + +// Provides a collection of tools for the QtGui module namespace QtGuiUtils { -// Color conversion +// Color conversion functions OCCT -> Qt QColor toQColor(const Quantity_Color& c); QColor toQColor(const Quantity_ColorRGBA& c); QColor toQColor(Quantity_NameOfColor c); -template -OTHER_COLOR_TYPE toColor(const QColor& c); +template +OtherColorType toColor(const QColor& c); -template +template Quantity_Color toColor(const QColor& c); Quantity_Color toPreferredColorSpace(const QColor& c); -// Image conversion +// Converts (OCCT)Image_Pixmap -> QPixmap QPixmap toQPixmap(const Image_PixMap& pixmap); // Returns linear interpolated color between 'a' and 'b' at parameter 't' @@ -77,23 +79,23 @@ class FontChange { // -- Implementation // -- -template -OTHER_COLOR_TYPE toColor(const QColor& c) { - if constexpr(std::is_same_v) { +template +OtherColorType toColor(const QColor& c) { + if constexpr(std::is_same_v) { return Quantity_Color(c.redF(), c.greenF(), c.blueF(), Quantity_TOC_RGB); } - if constexpr(std::is_same_v) { + if constexpr(std::is_same_v) { return Quantity_ColorRGBA(c.redF(), c.greenF(), c.blueF(), c.alphaF()); } - else if constexpr(std::is_same_v) { + else if constexpr(std::is_same_v) { return QtGuiUtils::toColor(c).Name(); } } -template +template Quantity_Color toColor(const QColor& c) { - return Quantity_Color(c.redF(), c.greenF(), c.blueF(), OTHER_COLOR_TYPE); + return Quantity_Color(c.redF(), c.greenF(), c.blueF(), OtherColorType); } } // namespace QtGuiUtils diff --git a/src/app/widgets_utils.cpp b/src/app/qtwidgets_utils.cpp similarity index 65% rename from src/app/widgets_utils.cpp rename to src/app/qtwidgets_utils.cpp index b6e68ddc..b5bb2469 100644 --- a/src/app/widgets_utils.cpp +++ b/src/app/qtwidgets_utils.cpp @@ -4,9 +4,8 @@ ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ -#include "widgets_utils.h" - -#include "../gui/qtgui_utils.h" +#include "qtwidgets_utils.h" +#include "qtgui_utils.h" #include #include @@ -17,28 +16,28 @@ namespace Mayo { -QScreen* WidgetsUtils::screen(const QWidget* widget) +QScreen* QtWidgetsUtils::screen(const QWidget* widget) { const QWindow* window = widget ? widget->windowHandle() : nullptr; return window ? window->screen() : QGuiApplication::primaryScreen(); } -int WidgetsUtils::screenPixelWidth(double screenRatio, const QWidget* parentWidget) +int QtWidgetsUtils::screenPixelWidth(double screenRatio, const QWidget* parentWidget) { - return QtGuiUtils::screenPixelWidth(screenRatio, WidgetsUtils::screen(parentWidget)); + return QtGuiUtils::screenPixelWidth(screenRatio, QtWidgetsUtils::screen(parentWidget)); } -int WidgetsUtils::screenPixelHeight(double screenRatio, const QWidget* parentWidget) +int QtWidgetsUtils::screenPixelHeight(double screenRatio, const QWidget* parentWidget) { - return QtGuiUtils::screenPixelHeight(screenRatio, WidgetsUtils::screen(parentWidget)); + return QtGuiUtils::screenPixelHeight(screenRatio, QtWidgetsUtils::screen(parentWidget)); } -QSize WidgetsUtils::screenPixelSize(double widthRatio, double heightRatio, const QWidget* parentWidget) +QSize QtWidgetsUtils::screenPixelSize(double widthRatio, double heightRatio, const QWidget* parentWidget) { - return QtGuiUtils::screenPixelSize(widthRatio, heightRatio, WidgetsUtils::screen(parentWidget)); + return QtGuiUtils::screenPixelSize(widthRatio, heightRatio, QtWidgetsUtils::screen(parentWidget)); } -void WidgetsUtils::addContentsWidget(QWidget* containerWidget, QWidget* contentsWidget) +void QtWidgetsUtils::addContentsWidget(QWidget* containerWidget, QWidget* contentsWidget) { if (containerWidget && contentsWidget) { if (!containerWidget->layout()) { @@ -51,7 +50,7 @@ void WidgetsUtils::addContentsWidget(QWidget* containerWidget, QWidget* contents } } -void WidgetsUtils::asyncMenuExec(QMenu* menu, const QPoint& pos) +void QtWidgetsUtils::asyncMenuExec(QMenu* menu, const QPoint& pos) { if (menu) { QObject::connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); @@ -59,7 +58,7 @@ void WidgetsUtils::asyncMenuExec(QMenu* menu, const QPoint& pos) } } -void WidgetsUtils::asyncDialogExec(QDialog* dialog) +void QtWidgetsUtils::asyncDialogExec(QDialog* dialog) { if (!dialog) return; @@ -68,46 +67,46 @@ void WidgetsUtils::asyncDialogExec(QDialog* dialog) dialog->show(); } -QMessageBox* WidgetsUtils::asyncMsgBoxInfo( +QMessageBox* QtWidgetsUtils::asyncMsgBoxInfo( QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons) { auto msgBox = new QMessageBox(QMessageBox::Information, title, text, buttons, parent); - WidgetsUtils::asyncDialogExec(msgBox); + QtWidgetsUtils::asyncDialogExec(msgBox); return msgBox; } -QMessageBox* WidgetsUtils::asyncMsgBoxWarning( +QMessageBox* QtWidgetsUtils::asyncMsgBoxWarning( QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons) { auto msgBox = new QMessageBox(QMessageBox::Warning, title, text, buttons, parent); - WidgetsUtils::asyncDialogExec(msgBox); + QtWidgetsUtils::asyncDialogExec(msgBox); return msgBox; } -QMessageBox* WidgetsUtils::asyncMsgBoxCritical( +QMessageBox* QtWidgetsUtils::asyncMsgBoxCritical( QWidget* parent, const QString& title, const QString& text, QMessageBox::StandardButtons buttons) { auto msgBox = new QMessageBox(QMessageBox::Critical, title, text, buttons, parent); - WidgetsUtils::asyncDialogExec(msgBox); + QtWidgetsUtils::asyncDialogExec(msgBox); return msgBox; } -void WidgetsUtils::moveWidgetRightTo(QWidget* widget, const QWidget* nextTo, int margin) +void QtWidgetsUtils::moveWidgetRightTo(QWidget* widget, const QWidget* nextTo, int margin) { const QRect frameGeom = nextTo->frameGeometry(); widget->move(nextTo->mapToParent(QPoint(frameGeom.width() + margin, 0))); } -void WidgetsUtils::moveWidgetLeftTo(QWidget* widget, const QWidget* nextTo, int margin) +void QtWidgetsUtils::moveWidgetLeftTo(QWidget* widget, const QWidget* nextTo, int margin) { const QRect frameGeom = widget->frameGeometry(); widget->move(nextTo->mapToParent(QPoint(-frameGeom.width() - margin, 0))); diff --git a/src/app/widgets_utils.h b/src/app/qtwidgets_utils.h similarity index 85% rename from src/app/widgets_utils.h rename to src/app/qtwidgets_utils.h index 15ad9681..5296f502 100644 --- a/src/app/widgets_utils.h +++ b/src/app/qtwidgets_utils.h @@ -14,7 +14,8 @@ class QWidget; namespace Mayo { -class WidgetsUtils { +// Provides a collection of tools for the QtWidgets module +class QtWidgetsUtils { public: static QScreen* screen(const QWidget* widget); static int screenPixelWidth(double screenRatio, const QWidget* parentWidget = nullptr); @@ -25,23 +26,32 @@ class WidgetsUtils { // If 'containerWidget' is empty then a QBoxLayout is created to receive 'contentsWidget' static void addContentsWidget(QWidget* containerWidget, QWidget* contentsWidget); + // Executes 'dialog' asynchronously(non blocking) + // The dialog object is automatically destroyed when execution is finished static void asyncDialogExec(QDialog* dialog); + + // Executes(popup) 'menu' asynchronously(non blocking) + // The menu object is automatically destroyed when execution is finished static void asyncMenuExec(QMenu* menu, const QPoint& pos = QCursor::pos()); + static QMessageBox* asyncMsgBoxInfo( QWidget* parent, const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::Ok); + QMessageBox::StandardButtons buttons = QMessageBox::Ok + ); static QMessageBox* asyncMsgBoxWarning( QWidget* parent, const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::Ok); + QMessageBox::StandardButtons buttons = QMessageBox::Ok + ); static QMessageBox* asyncMsgBoxCritical( QWidget* parent, const QString& title, const QString& text, - QMessageBox::StandardButtons buttons = QMessageBox::Ok); + QMessageBox::StandardButtons buttons = QMessageBox::Ok + ); // Move position of 'widget' so it's displayed stuck to the right of 'nextTo' static void moveWidgetRightTo(QWidget* widget, const QWidget* nextTo, int margin); diff --git a/src/app/recent_files.cpp b/src/app/recent_files.cpp index e9988fd8..661964e4 100644 --- a/src/app/recent_files.cpp +++ b/src/app/recent_files.cpp @@ -9,10 +9,10 @@ #include "../base/meta_enum.h" #include "../graphics/graphics_utils.h" #include "../gui/gui_document.h" -#include "../gui/qtgui_utils.h" #include "../io_image/io_image.h" #include "filepath_conv.h" #include "qstring_conv.h" +#include "qtgui_utils.h" #include "theme.h" #include @@ -62,7 +62,7 @@ int64_t RecentFile::timestampLastModified(const FilePath& fp) { // Qt: QFileInfo(filepath).lastModified().toSecsSinceEpoch(); try { - const auto lastModifiedTime = std::filesystem::last_write_time(fp).time_since_epoch(); + const auto lastModifiedTime = filepathLastWriteTime(fp).time_since_epoch(); return std::chrono::duration_cast(lastModifiedTime).count(); } catch (const std::exception& err) { qDebug() << fmt::format("Exception caught\n Function {}\n Filepath: {}\n Error: {}", diff --git a/src/app/recent_files.h b/src/app/recent_files.h index e996f480..706f1a1c 100644 --- a/src/app/recent_files.h +++ b/src/app/recent_files.h @@ -17,6 +17,7 @@ namespace Mayo { class GuiDocument; +// Provides information about a "recently" opened file struct RecentFile { FilePath filepath; QPixmap thumbnail; @@ -26,13 +27,25 @@ struct RecentFile { static int64_t timestampLastModified(const FilePath& fp); }; +// Alias for "array of RecentFile objects" using RecentFiles = std::vector; + +// Alias for Property type owning an array of RecentFile objects +// This is useful to store recent files into application settings using PropertyRecentFiles = GenericProperty; bool operator==(const RecentFile& lhs, const RecentFile& rhs); + +// Writes a RecentFile object to QDataStream QDataStream& operator<<(QDataStream& stream, const RecentFile& recentFile); -QDataStream& operator>>(QDataStream& stream, RecentFile& recentFile); + +// Writes array of RecentFile objects to QDataStream QDataStream& operator<<(QDataStream& stream, const RecentFiles& recentFiles); + +// Extracts a RecentFile object from QDataStream +QDataStream& operator>>(QDataStream& stream, RecentFile& recentFile); + +// Extracts array of RecentFile objects from QDataStream QDataStream& operator>>(QDataStream& stream, RecentFiles& recentFiles); } // namespace Mayo diff --git a/src/app/theme.cpp b/src/app/theme.cpp index f554716c..59e27789 100644 --- a/src/app/theme.cpp +++ b/src/app/theme.cpp @@ -100,6 +100,8 @@ static QString iconFileName(Theme::Icon icn) case Theme::Icon::View3dBottom: return "view-bottom.svg"; case Theme::Icon::View3dFront: return "view-front.svg"; case Theme::Icon::View3dBack: return "view-back.svg"; + case Theme::Icon::TurnClockwise: return "turn-cw.svg"; + case Theme::Icon::TurnCounterClockwise: return "turn-ccw.svg"; case Theme::Icon::ItemMesh: return "item-mesh.svg"; case Theme::Icon::ItemXde: return "item-xde.svg"; case Theme::Icon::XdeAssembly: return "xde-assembly.svg"; diff --git a/src/app/theme.h b/src/app/theme.h index 683d8a24..fe1fd569 100644 --- a/src/app/theme.h +++ b/src/app/theme.h @@ -67,6 +67,8 @@ class Theme { View3dBottom, View3dFront, View3dBack, + TurnClockwise, + TurnCounterClockwise, // ItemMesh, ItemXde, diff --git a/src/app/widget_clip_planes.cpp b/src/app/widget_clip_planes.cpp index 636842cf..563e0e68 100644 --- a/src/app/widget_clip_planes.cpp +++ b/src/app/widget_clip_planes.cpp @@ -21,13 +21,14 @@ #include #include #include +#include namespace Mayo { -WidgetClipPlanes::WidgetClipPlanes(const Handle_V3d_View& view3d, QWidget* parent) +WidgetClipPlanes::WidgetClipPlanes(GraphicsViewPtr view, QWidget* parent) : QWidget(parent), m_ui(new Ui_WidgetClipPlanes), - m_view(view3d) + m_view(view) { m_ui->setupUi(this); this->createPlaneCappingTexture(); @@ -61,13 +62,12 @@ WidgetClipPlanes::WidgetClipPlanes(const Handle_V3d_View& view3d, QWidget* paren data.graphics->SetCappingTexture(m_textureCapping); } - const auto settings = appModule->settings(); - QObject::connect(settings, &Settings::changed, this, [=](Property* property) { + appModule->settings()->signalChanged.connectSlot([=](const Property* property) { if (property == &appModule->properties()->clipPlanesCappingOn) { for (ClipPlaneData& data : m_vecClipPlaneData) data.graphics->SetCapping(appModule->properties()->clipPlanesCappingOn); - m_view->Redraw(); + m_view.redraw(); } else if (property == &appModule->properties()->clipPlanesCappingHatchOn) { Handle_Graphic3d_TextureMap hatchTexture; @@ -77,9 +77,10 @@ WidgetClipPlanes::WidgetClipPlanes(const Handle_V3d_View& view3d, QWidget* paren for (ClipPlaneData& data : m_vecClipPlaneData) data.graphics->SetCappingTexture(hatchTexture); - m_view->Redraw(); + m_view.redraw(); } }); + m_ui->widget_CustomDir->setVisible(false); } @@ -101,7 +102,7 @@ void WidgetClipPlanes::setRanges(const Bnd_Box& bndBox) data.ui.check_On->setChecked(false); } - m_view->Redraw(); + m_view.redraw(); } void WidgetClipPlanes::setClippingOn(bool on) @@ -109,7 +110,7 @@ void WidgetClipPlanes::setClippingOn(bool on) for (ClipPlaneData& data : m_vecClipPlaneData) data.graphics->SetOn(on ? data.ui.check_On->isChecked() : false); - m_view->Redraw(); + m_view.redraw(); } void WidgetClipPlanes::connectUi(ClipPlaneData* data) @@ -123,7 +124,7 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) QObject::connect(ui.check_On, &QCheckBox::clicked, this, [=](bool on) { ui.widget_Control->setEnabled(on); this->setPlaneOn(gfx, on); - m_view->Redraw(); + m_view.redraw(); }); if (data->ui.customXDirSpin()) { @@ -147,7 +148,7 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) const double dPct = ui.spinValueToSliderValue(pos); posSlider->setValue(qRound(dPct)); GraphicsUtils::Gfx3dClipPlane_setPosition(gfx, pos); - m_view->Redraw(); + m_view.redraw(); }); QObject::connect(posSlider, &QSlider::valueChanged, this, [=](int pct) { @@ -155,14 +156,14 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) QSignalBlocker sigBlock(posSpin); Q_UNUSED(sigBlock); posSpin->setValue(pos); GraphicsUtils::Gfx3dClipPlane_setPosition(gfx, pos); - m_view->Redraw(); + m_view.redraw(); }); QObject::connect(ui.inverseBtn(), &QAbstractButton::clicked, this, [=]{ const gp_Dir invNormal = gfx->ToPlane().Axis().Direction().Reversed(); GraphicsUtils::Gfx3dClipPlane_setNormal(gfx, invNormal); GraphicsUtils::Gfx3dClipPlane_setPosition(gfx, data->ui.posSpin()->value()); - m_view->Redraw(); + m_view.redraw(); }); // Custom plane normal @@ -180,7 +181,7 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) const auto bbc = BndBoxCoords::get(m_bndBox); this->setPlaneRange(data, MathUtils::planeRange(bbc, normal)); GraphicsUtils::Gfx3dClipPlane_setNormal(gfx, normal); - m_view->Redraw(); + m_view.redraw(); } }); }; @@ -203,8 +204,8 @@ void WidgetClipPlanes::connectUi(ClipPlaneData* data) void WidgetClipPlanes::setPlaneOn(const Handle_Graphic3d_ClipPlane& plane, bool on) { plane->SetOn(on); - if (!GraphicsUtils::V3dView_hasClipPlane(m_view, plane)) - m_view->AddClipPlane(plane); + if (!GraphicsUtils::V3dView_hasClipPlane(m_view.v3dView(), plane)) + m_view.v3dView()->AddClipPlane(plane); } void WidgetClipPlanes::setPlaneRange(ClipPlaneData* data, const Range& range) diff --git a/src/app/widget_clip_planes.h b/src/app/widget_clip_planes.h index 08339f8a..9375b667 100644 --- a/src/app/widget_clip_planes.h +++ b/src/app/widget_clip_planes.h @@ -6,23 +6,24 @@ #pragma once +#include "../graphics/graphics_view_ptr.h" + #include #include #include #include -#include #include +class QAbstractButton; +class QAbstractSlider; class QCheckBox; class QDoubleSpinBox; -class QAbstractSlider; -class QAbstractButton; namespace Mayo { class WidgetClipPlanes : public QWidget { Q_OBJECT public: - WidgetClipPlanes(const Handle_V3d_View& view3d, QWidget* parent = nullptr); + WidgetClipPlanes(GraphicsViewPtr view, QWidget* parent = nullptr); ~WidgetClipPlanes(); void setRanges(const Bnd_Box& box); @@ -59,7 +60,7 @@ class WidgetClipPlanes : public QWidget { void createPlaneCappingTexture(); class Ui_WidgetClipPlanes* m_ui; - Handle_V3d_View m_view; + GraphicsViewPtr m_view; std::vector m_vecClipPlaneData; Bnd_Box m_bndBox; Handle_Graphic3d_TextureMap m_textureCapping; diff --git a/src/app/widget_file_system.cpp b/src/app/widget_file_system.cpp index bf0e21e8..7ee64185 100644 --- a/src/app/widget_file_system.cpp +++ b/src/app/widget_file_system.cpp @@ -39,7 +39,8 @@ WidgetFileSystem::WidgetFileSystem(QWidget* parent) QObject::connect( m_treeWidget, &QTreeWidget::itemActivated, - this, &WidgetFileSystem::onTreeItemActivated); + this, &WidgetFileSystem::onTreeItemActivated + ); } QFileInfo WidgetFileSystem::currentLocation() const @@ -51,48 +52,14 @@ void WidgetFileSystem::setLocation(const QFileInfo& fiLoc) { const QString pathCurrLocation = Internal::absolutePath(m_location); const QString pathLoc = Internal::absolutePath(fiLoc); - if (pathCurrLocation == pathLoc) { - for (int i = 0; i < m_treeWidget->topLevelItemCount(); ++i) { - QTreeWidgetItem* item = m_treeWidget->topLevelItem(i); - if (item->text(0) == fiLoc.fileName()) { - m_treeWidget->clearSelection(); - item->setSelected(true); - break; - } - } + if (pathCurrLocation == pathLoc && fiLoc.isFile()) { + this->selectFileInCurrentFolder(fiLoc.fileName()); } else { - m_treeWidget->clear(); - QTreeWidgetItem* itemToBeSelected = nullptr; - QList listItem; - if (fiLoc.exists()) { - const QDir dir(pathLoc); - const QFileInfoList listEntryFileInfo = - dir.entryInfoList( - QDir::Files | QDir::AllDirs | QDir::NoDot, - QDir::DirsFirst); - for (const QFileInfo& fi : listEntryFileInfo) { - auto item = new QTreeWidgetItem; - if (fi.fileName() != QLatin1String(".")) { - item->setText(0, fi.fileName()); - item->setIcon(0, m_fileIconProvider.icon(fi)); - const QString itemTooltip = - tr("%1\nSize: %2\nLast modified: %3") - .arg(QDir::toNativeSeparators(fi.absoluteFilePath())) - .arg(QStringUtils::bytesText(fi.size())) - .arg(QLocale::system().toString(fi.lastModified(), QLocale::LongFormat)); - item->setToolTip(0, itemTooltip); - if (fi.fileName() == fiLoc.fileName()) - itemToBeSelected = item; - } - listItem.push_back(item); - } - } - m_treeWidget->addTopLevelItems(listItem); - m_treeWidget->headerItem()->setText(0, fiLoc.dir().dirName()); - if (itemToBeSelected != nullptr) - itemToBeSelected->setSelected(true); + this->setCurrentFolder(pathLoc); + this->selectFileInCurrentFolder(fiLoc.fileName()); } + m_location = fiLoc; } @@ -100,15 +67,62 @@ void WidgetFileSystem::onTreeItemActivated(QTreeWidgetItem* item, int column) { if (item != nullptr && column == 0) { if (item->text(0) == QLatin1String("..")) { - this->setLocation(m_location.absoluteDir().path()); + QDir dir = Internal::absolutePath(m_location); + dir.cdUp(); + this->setCurrentFolder(dir); + m_location = dir.absolutePath(); } else { const QDir dir(Internal::absolutePath(m_location)); const QFileInfo fi(dir, item->text(0)); - if (fi.isDir()) + if (fi.isDir()) { this->setLocation(fi); - else + } + else { emit this->locationActivated(fi); + m_location = fi; + } + } + } +} + +void WidgetFileSystem::setCurrentFolder(const QDir& dir) +{ + m_treeWidget->clear(); + QList listItem; + if (dir.exists()) { + const QFileInfoList listEntryFileInfo = + dir.entryInfoList( + QDir::Files | QDir::AllDirs | QDir::NoDot, + QDir::DirsFirst); + for (const QFileInfo& fi : listEntryFileInfo) { + auto item = new QTreeWidgetItem; + if (fi.fileName() != QLatin1String(".")) { + item->setText(0, fi.fileName()); + item->setIcon(0, m_fileIconProvider.icon(fi)); + const QString itemTooltip = + tr("%1\nSize: %2\nLast modified: %3") + .arg(QDir::toNativeSeparators(fi.absoluteFilePath())) + .arg(QStringUtils::bytesText(fi.size())) + .arg(QLocale::system().toString(fi.lastModified(), QLocale::LongFormat)); + item->setToolTip(0, itemTooltip); + } + + listItem.push_back(item); + } + } + + m_treeWidget->addTopLevelItems(listItem); + m_treeWidget->headerItem()->setText(0, dir.dirName()); +} + +void WidgetFileSystem::selectFileInCurrentFolder(const QString& fileName) +{ + for (int i = 0; i < m_treeWidget->topLevelItemCount(); ++i) { + if (m_treeWidget->topLevelItem(i)->text(0) == fileName) { + m_treeWidget->clearSelection(); + m_treeWidget->topLevelItem(i)->setSelected(true); + return; } } } diff --git a/src/app/widget_file_system.h b/src/app/widget_file_system.h index 17ae60ab..a541e018 100644 --- a/src/app/widget_file_system.h +++ b/src/app/widget_file_system.h @@ -27,6 +27,8 @@ class WidgetFileSystem : public QWidget { private: void onTreeItemActivated(QTreeWidgetItem* item, int column); + void setCurrentFolder(const QDir& dir); + void selectFileInCurrentFolder(const QString& fileName); QTreeWidget* m_treeWidget = nullptr; QFileInfo m_location; diff --git a/src/app/widget_gui_document.cpp b/src/app/widget_gui_document.cpp index 96db0fc9..949bb4d4 100644 --- a/src/app/widget_gui_document.cpp +++ b/src/app/widget_gui_document.cpp @@ -7,9 +7,10 @@ #include "widget_gui_document.h" #include "../base/cpp_utils.h" +#include "../base/unit_system.h" #include "../graphics/graphics_utils.h" -#include "../graphics/v3d_view_camera_animation.h" #include "../gui/gui_document.h" +#include "../gui/v3d_view_camera_animation.h" #include "button_flat.h" #include "theme.h" #include "widget_clip_planes.h" @@ -17,9 +18,11 @@ #include "widget_measure.h" #include "widget_occ_view.h" #include "widget_occ_view_controller.h" -#include "widgets_utils.h" +#include "qtwidgets_utils.h" #include +#include +#include #include #include #include @@ -30,8 +33,61 @@ namespace Mayo { -namespace Internal { +namespace { +// Provides implementation of IAnimationBackend based on QAbstractAnimation +class QtAnimationBackend : public IAnimationBackend { +public: + QtAnimationBackend(QEasingCurve::Type easingType = QEasingCurve::Linear) + : m_easingCurve(easingType) + { + } + + void setDuration(QuantityTime t) override { + m_impl.m_duration_ms = UnitSystem::milliseconds(t); + } + + bool isRunning() const override { + return m_impl.state() == QAbstractAnimation::Running; + } + + void start() override { + m_impl.start(QAbstractAnimation::KeepWhenStopped); + } + + void stop() override { + m_impl.stop(); + } + + double valueForProgress(double p) const override { + return m_easingCurve.valueForProgress(p); + } + + void setTimerCallback(std::function fn) override { + m_impl.m_callback = std::move(fn); + } + +private: + class AnimationImpl : public QAbstractAnimation { + public: + double m_duration_ms = 1000.; + std::function m_callback; + + int duration() const override { + return static_cast(m_duration_ms); + } + + protected: + void updateCurrentTime(int currentTime) override { + m_callback(currentTime * Quantity_Millisecond); + } + }; + + AnimationImpl m_impl; + QEasingCurve m_easingCurve; +}; + +// Provides an overlay widget to be used within 3D view class PanelView3d : public QWidget { public: PanelView3d(WidgetGuiDocument* parent = nullptr) @@ -49,6 +105,7 @@ class PanelView3d : public QWidget { } }; +// Provides style to redefine icon size of menu items class MenuIconSizeStyle : public QProxyStyle { public: void setMenuIconSize(int size) { @@ -67,9 +124,10 @@ class MenuIconSizeStyle : public QProxyStyle { int m_menuIconSize = -1; }; -const int widgetMargin = 4; +// Default margin to be used in widgets +const int Internal_widgetMargin = 4; -} // namespace Internal +} // namespace WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) : QWidget(parent), @@ -85,7 +143,7 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) auto widgetBtnsContents = new QWidget; auto layoutBtns = new QHBoxLayout(widgetBtnsContents); - layoutBtns->setSpacing(Internal::widgetMargin + 2); + layoutBtns->setSpacing(Internal_widgetMargin + 2); layoutBtns->setContentsMargins(2, 2, 2, 2); m_btnFitAll = this->createViewBtn(widgetBtnsContents, Theme::Icon::Expand, tr("Fit All")); m_btnEditClipping = this->createViewBtn(widgetBtnsContents, Theme::Icon::ClipPlane, tr("Edit clip planes")); @@ -103,6 +161,10 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) m_widgetBtns = this->createWidgetPanelContainer(widgetBtnsContents); auto gfxScene = m_guiDoc->graphicsScene(); + gfxScene->signalRedrawRequested.connectSlot([=](const Handle_V3d_View& view) { + if (view == m_qtOccView->v3dView()) + m_qtOccView->redraw(); + }); QObject::connect(m_btnFitAll, &ButtonFlat::clicked, this, [=]{ m_guiDoc->runViewCameraAnimation(&GraphicsUtils::V3dView_fitAll); }); @@ -118,31 +180,31 @@ WidgetGuiDocument::WidgetGuiDocument(GuiDocument* guiDoc, QWidget* parent) m_btnMeasure, &ButtonFlat::checked, this, &WidgetGuiDocument::toggleWidgetMeasure ); - QObject::connect( - m_controller, &V3dViewController::dynamicActionStarted, - m_guiDoc, &GuiDocument::stopViewCameraAnimation - ); - QObject::connect( - m_controller, &V3dViewController::viewScaled, - m_guiDoc, &GuiDocument::stopViewCameraAnimation - ); - QObject::connect(m_controller, &V3dViewController::mouseClicked, this, [=](Qt::MouseButton btn) { - if (btn == Qt::LeftButton && !m_guiDoc->processAction(gfxScene->currentHighlightedOwner())) { + m_controller->signalDynamicActionStarted.connectSlot([=]{ m_guiDoc->stopViewCameraAnimation(); }); + m_controller->signalViewScaled.connectSlot([=]{ m_guiDoc->stopViewCameraAnimation(); }); + m_controller->signalMouseButtonClicked.connectSlot([=](Aspect_VKeyMouse btn) { + if (btn == Aspect_VKeyMouse_LeftButton && !m_guiDoc->processAction(gfxScene->currentHighlightedOwner())) { gfxScene->select(); m_qtOccView->redraw(); } }); - QObject::connect(m_controller, &WidgetOccViewController::multiSelectionToggled, this, [=](bool on) { + m_controller->signalMultiSelectionToggled.connectSlot([=](bool on) { auto mode = on ? GraphicsScene::SelectionMode::Multi : GraphicsScene::SelectionMode::Single; gfxScene->setSelectionMode(mode); }); + m_guiDoc->viewCameraAnimation()->setBackend(std::make_unique(QEasingCurve::OutExpo)); m_guiDoc->viewCameraAnimation()->setRenderFunction([=](const Handle_V3d_View& view){ if (view == m_qtOccView->v3dView()) m_qtOccView->redraw(); }); } +Document::Identifier WidgetGuiDocument::documentIdentifier() const +{ + return m_guiDoc->document()->identifier(); +} + QColor WidgetGuiDocument::panelBackgroundColor() const { QColor color = mayoTheme()->color(Theme::Color::Palette_Window); @@ -163,8 +225,8 @@ void WidgetGuiDocument::resizeEvent(QResizeEvent* event) QWidget* WidgetGuiDocument::createWidgetPanelContainer(QWidget* widgetContents) { - auto panel = new Internal::PanelView3d(this); - WidgetsUtils::addContentsWidget(panel, widgetContents); + auto panel = new PanelView3d(this); + QtWidgetsUtils::addContentsWidget(panel, widgetContents); panel->show(); panel->adjustSize(); return panel; @@ -185,12 +247,9 @@ void WidgetGuiDocument::toggleWidgetClipPlanes(bool on) m_widgetClipPlanes->setClippingOn(on); } else if (on) { - m_widgetClipPlanes = new WidgetClipPlanes(m_guiDoc->v3dView()); + m_widgetClipPlanes = new WidgetClipPlanes(m_guiDoc->graphicsView()); this->createWidgetPanelContainer(m_widgetClipPlanes); - QObject::connect( - m_guiDoc, &GuiDocument::graphicsBoundingBoxChanged, - m_widgetClipPlanes, &WidgetClipPlanes::setRanges - ); + m_guiDoc->signalGraphicsBoundingBoxChanged.connectSlot(&WidgetClipPlanes::setRanges, m_widgetClipPlanes); m_widgetClipPlanes->setRanges(m_guiDoc->graphicsBoundingBox()); } @@ -242,22 +301,22 @@ void WidgetGuiDocument::layoutWidgetPanel(QWidget* panel) { auto fnPanelPos = [=](QWidget* panel) -> QPoint { const QRect ctrlRect = this->viewControlsRect(); - const int margin = Internal::widgetMargin; + const int margin = Internal_widgetMargin; if (m_guiDoc->viewTrihedronMode() != GuiDocument::ViewTrihedronMode::AisViewCube) return QPoint(margin, ctrlRect.bottom() + margin); switch (m_guiDoc->viewTrihedronCorner()) { - case Qt::TopLeftCorner: + case Aspect_TOTP_LEFT_UPPER: return QPoint(ctrlRect.left(), ctrlRect.bottom() + margin); - case Qt::TopRightCorner: + case Aspect_TOTP_RIGHT_UPPER: return QPoint(this->width() - panel->width(), ctrlRect.bottom() + margin); - case Qt::BottomLeftCorner: + case Aspect_TOTP_LEFT_LOWER: return QPoint(margin, ctrlRect.top() - panel->height() - margin); - case Qt::BottomRightCorner: + case Aspect_TOTP_RIGHT_LOWER: return QPoint(this->width() - panel->width(), ctrlRect.top() - panel->height() - margin); + default: + return QPoint(margin, ctrlRect.bottom() + margin); } // endswitch - - return QPoint(margin, ctrlRect.bottom() + margin); }; QWidget* widget = panel ? panel->parentWidget() : nullptr; @@ -305,9 +364,9 @@ void WidgetGuiDocument::recreateMenuViewProjections(QWidget* container) { V3d_Zneg, Theme::Icon::View3dBottom, tr("Bottom") } }; if (m_guiDoc->viewTrihedronMode() == GuiDocument::ViewTrihedronMode::AisViewCube) { - static Internal::MenuIconSizeStyle* menuStyle = nullptr; + static MenuIconSizeStyle* menuStyle = nullptr; if (!menuStyle) { - menuStyle = new Internal::MenuIconSizeStyle; + menuStyle = new MenuIconSizeStyle; menuStyle->setMenuIconSize(m_btnFitAll->iconSize().width()); } @@ -366,23 +425,25 @@ QRect WidgetGuiDocument::viewControlsRect() const void WidgetGuiDocument::layoutViewControls() { - const int margin = Internal::widgetMargin + 2; + const int margin = Internal_widgetMargin + 2; auto fnGetViewControlsPos = [=]() -> QPoint { if (m_guiDoc->viewTrihedronMode() == GuiDocument::ViewTrihedronMode::AisViewCube) { const int btnSize = m_btnFitAll->width(); - const int viewCubeBndSize = m_guiDoc->aisViewCubeBoundingSize(); + const int viewCubeBndSize = m_guiDoc->aisViewCubeBoundingSize() / m_guiDoc->devicePixelRatio(); const int ctrlHeight = btnSize; const int ctrlXOffset = margin; switch (m_guiDoc->viewTrihedronCorner()) { - case Qt::TopLeftCorner: + case Aspect_TOTP_LEFT_UPPER: return { ctrlXOffset, viewCubeBndSize + margin }; - case Qt::TopRightCorner: + case Aspect_TOTP_RIGHT_UPPER: return { this->width() - viewCubeBndSize + ctrlXOffset, viewCubeBndSize + margin }; - case Qt::BottomLeftCorner: + case Aspect_TOTP_LEFT_LOWER: return { ctrlXOffset, this->height() - viewCubeBndSize - margin - ctrlHeight }; - case Qt::BottomRightCorner: + case Aspect_TOTP_RIGHT_LOWER: return { this->width() - viewCubeBndSize + ctrlXOffset, this->height() - viewCubeBndSize - margin - ctrlHeight }; + default: + return { margin, margin }; } // endswitch } diff --git a/src/app/widget_gui_document.h b/src/app/widget_gui_document.h index bdb63718..bdc4abf2 100644 --- a/src/app/widget_gui_document.h +++ b/src/app/widget_gui_document.h @@ -6,8 +6,9 @@ #pragma once -#include "widget_occ_view_controller.h" +#include "../base/document.h" #include "theme.h" +#include "widget_occ_view_controller.h" #include #include @@ -31,6 +32,8 @@ class WidgetGuiDocument : public QWidget { WidgetOccViewController* controller() const { return m_controller; } IWidgetOccView* view() const { return m_qtOccView; } + Document::Identifier documentIdentifier() const; + QColor panelBackgroundColor() const; protected: diff --git a/src/app/widget_home_files.cpp b/src/app/widget_home_files.cpp index 4a8d0015..f6be0ef2 100644 --- a/src/app/widget_home_files.cpp +++ b/src/app/widget_home_files.cpp @@ -134,7 +134,7 @@ class HomeFilesModel : public ListHelper::Model { return WidgetHomeFiles::tr("%1 days ago %2").arg(diffDays).arg(strTime); } else { - const QString strDate = appModule->locale().toString(date, QLocale::ShortFormat); + const QString strDate = appModule->qtLocale().toString(date, QLocale::ShortFormat); return WidgetHomeFiles::tr("%1 %2").arg(strDate, strTime); } }; @@ -152,7 +152,7 @@ class HomeFilesModel : public ListHelper::Model { "Modified: %4\n" "Read: %5\n") .arg(QDir::toNativeSeparators(fi.absolutePath())) - .arg(QStringUtils::bytesText(fi.size(), appModule->locale())) + .arg(QStringUtils::bytesText(fi.size(), appModule->qtLocale())) .arg(fnToString(fi.birthTime())) .arg(fnToString(fi.lastModified())) .arg(fnToString(fi.lastRead())) @@ -222,7 +222,7 @@ WidgetHomeFiles::WidgetHomeFiles(QWidget* parent) m_gridDelegate->setItemPixmapSize(appModule->recentFileThumbnailSize()); m_gridView->setItemDelegate(m_gridDelegate); - QObject::connect(appModule->settings(), &Settings::changed, this, [=](const Property* setting) { + appModule->settings()->signalChanged.connectSlot([=](const Property* setting) { if (setting == &appModule->properties()->recentFiles) model->reload(); }); diff --git a/src/app/widget_measure.cpp b/src/app/widget_measure.cpp index 1ec7abc9..def467db 100644 --- a/src/app/widget_measure.cpp +++ b/src/app/widget_measure.cpp @@ -5,19 +5,20 @@ ****************************************************************************/ #include "widget_measure.h" -#include "measure_display.h" -#include "measure_tool_brep.h" +#include "app_module.h" #include "qstring_conv.h" #include "theme.h" #include "ui_widget_measure.h" #include "../base/unit_system.h" #include "../gui/gui_document.h" +#include "../measure/measure_tool_brep.h" #include #include #include +#include #include namespace Mayo { @@ -47,8 +48,8 @@ IMeasureTool* findSupportingMeasureTool(const GraphicsObjectPtr& gfxObject, Meas // Helper function to iterate and execute function 'fn' on all the graphics objects owned by a // measure display -template -void foreachGraphicsObject(const IMeasureDisplay* ptr, FUNCTION fn) +template +void foreachGraphicsObject(const IMeasureDisplay* ptr, Function fn) { if (ptr) { const int count = ptr->graphicsObjectsCount(); @@ -58,8 +59,8 @@ void foreachGraphicsObject(const IMeasureDisplay* ptr, FUNCTION fn) } // Overload of the helper function above -template -void foreachGraphicsObject(const std::unique_ptr& ptr, FUNCTION fn) { +template +void foreachGraphicsObject(const std::unique_ptr& ptr, Function fn) { foreachGraphicsObject(ptr.get(), fn); } @@ -106,10 +107,8 @@ void WidgetMeasure::setMeasureOn(bool on) auto gfxScene = m_guiDoc->graphicsScene(); if (on) { this->onMeasureTypeChanged(m_ui->combo_MeasureType->currentIndex()); - m_connGraphicsSelectionChanged = QObject::connect( - gfxScene, &GraphicsScene::selectionChanged, - this, &WidgetMeasure::onGraphicsSelectionChanged - ); + m_connGraphicsSelectionChanged = + gfxScene->signalSelectionChanged.connectSlot(&WidgetMeasure::onGraphicsSelectionChanged, this); } else { gfxScene->foreachDisplayedObject([=](const GraphicsObjectPtr& gfxObject) { @@ -117,7 +116,7 @@ void WidgetMeasure::setMeasureOn(bool on) gfxScene->activateObjectSelection(gfxObject, 0); }); gfxScene->clearSelection(); - QObject::disconnect(m_connGraphicsSelectionChanged); + m_connGraphicsSelectionChanged.disconnect(); } } @@ -216,7 +215,7 @@ void WidgetMeasure::onMeasureTypeChanged(int id) void WidgetMeasure::onMeasureUnitsChanged() { - const MeasureConfig config = this->currentMeasureConfig(); + const MeasureDisplayConfig config = this->currentMeasureDisplayConfig(); for (IMeasureDisplayPtr& measure : m_vecMeasureDisplay) { measure->update(config); foreachGraphicsObject(measure, [](const GraphicsObjectPtr& gfxObject) { @@ -233,13 +232,16 @@ MeasureType WidgetMeasure::currentMeasureType() const return WidgetMeasure::toMeasureType(m_ui->combo_MeasureType->currentIndex()); } -MeasureConfig WidgetMeasure::currentMeasureConfig() const +MeasureDisplayConfig WidgetMeasure::currentMeasureDisplayConfig() const { - return { - WidgetMeasure::toMeasureLengthUnit(m_ui->combo_LengthUnit->currentIndex()), - WidgetMeasure::toMeasureAngleUnit(m_ui->combo_AngleUnit->currentIndex()), - WidgetMeasure::toMeasureAreaUnit(m_ui->combo_AreaUnit->currentIndex()) - }; + MeasureDisplayConfig cfg; + cfg.lengthUnit = WidgetMeasure::toMeasureLengthUnit(m_ui->combo_LengthUnit->currentIndex()); + cfg.angleUnit = WidgetMeasure::toMeasureAngleUnit(m_ui->combo_AngleUnit->currentIndex()); + cfg.areaUnit = WidgetMeasure::toMeasureAreaUnit(m_ui->combo_AreaUnit->currentIndex()); + cfg.doubleToStringOptions.locale = AppModule::get()->stdLocale(); + cfg.doubleToStringOptions.decimalCount = AppModule::get()->defaultTextOptions().unitDecimals; + cfg.devicePixelRatio = this->devicePixelRatioF(); + return cfg; } void WidgetMeasure::onGraphicsSelectionChanged() @@ -328,7 +330,8 @@ void WidgetMeasure::onGraphicsSelectionChanged() // Display new measure graphics objects for (IMeasureDisplayPtr& measure : vecNewMeasureDisplay) { - measure->update(this->currentMeasureConfig()); + measure->update(this->currentMeasureDisplayConfig()); + measure->adaptGraphics(gfxScene->v3dViewer()->Driver()); foreachGraphicsObject(measure, [=](const GraphicsObjectPtr& gfxObject) { gfxObject->SetZLayer(Graphic3d_ZLayerId_Topmost); gfxScene->addObject(gfxObject); @@ -391,7 +394,7 @@ void WidgetMeasure::updateMessagePanel() for (const IMeasureDisplayPtr& measure : m_vecMeasureDisplay) sumMeasure->sumAdd(*measure); - sumMeasure->update(this->currentMeasureConfig()); + sumMeasure->update(this->currentMeasureDisplayConfig()); fnAddMeasureText(sumMeasure); } } diff --git a/src/app/widget_measure.h b/src/app/widget_measure.h index 464a1dc2..c43d5412 100644 --- a/src/app/widget_measure.h +++ b/src/app/widget_measure.h @@ -6,8 +6,9 @@ #pragma once -#include "measure_display.h" -#include "measure_tool.h" +#include "../base/signal.h" +#include "../measure/measure_display.h" +#include "../measure/measure_tool.h" #include #include @@ -42,7 +43,7 @@ class WidgetMeasure : public QWidget { static AreaUnit toMeasureAreaUnit(int comboBoxId); MeasureType currentMeasureType() const; - MeasureConfig currentMeasureConfig() const; + MeasureDisplayConfig currentMeasureDisplayConfig() const; void onGraphicsSelectionChanged(); @@ -68,7 +69,7 @@ class WidgetMeasure : public QWidget { std::vector m_vecLinkGfxOwnerMeasure; IMeasureTool* m_tool = nullptr; QString m_errorMessage; - QMetaObject::Connection m_connGraphicsSelectionChanged; + SignalConnectionHandle m_connGraphicsSelectionChanged; }; } // namespace Mayo diff --git a/src/app/widget_message_indicator.cpp b/src/app/widget_message_indicator.cpp index 3d476625..b960b788 100644 --- a/src/app/widget_message_indicator.cpp +++ b/src/app/widget_message_indicator.cpp @@ -5,7 +5,7 @@ ****************************************************************************/ #include "widget_message_indicator.h" -#include "../gui/qtgui_utils.h" +#include "qtgui_utils.h" #include "theme.h" #include diff --git a/src/app/widget_model_tree.cpp b/src/app/widget_model_tree.cpp index de5380f4..3089d190 100644 --- a/src/app/widget_model_tree.cpp +++ b/src/app/widget_model_tree.cpp @@ -195,34 +195,19 @@ void WidgetModelTree::registerGuiApplication(GuiApplication* guiApp) m_guiApp = guiApp; auto app = guiApp->application(); - QObject::connect( - app.get(), &Application::documentAdded, - this, &WidgetModelTree::onDocumentAdded); - QObject::connect( - app.get(), &Application::documentAboutToClose, - this, &WidgetModelTree::onDocumentAboutToClose); - QObject::connect( - app.get(), &Application::documentNameChanged, - this, &WidgetModelTree::onDocumentNameChanged); - QObject::connect( - app.get(), &Application::documentEntityAdded, - this, &WidgetModelTree::onDocumentEntityAdded); - QObject::connect( - app.get(), &Application::documentEntityAboutToBeDestroyed, - this, &WidgetModelTree::onDocumentEntityAboutToBeDestroyed); - - QObject::connect(m_guiApp, &GuiApplication::guiDocumentAdded, this, [=](GuiDocument* guiDoc) { - QObject::connect( - guiDoc, &GuiDocument::nodesVisibilityChanged, - this, [=](const std::unordered_map& mapNodeId) { + app->signalDocumentAdded.connectSlot(&WidgetModelTree::onDocumentAdded, this); + app->signalDocumentAboutToClose.connectSlot(&WidgetModelTree::onDocumentAboutToClose, this); + app->signalDocumentNameChanged.connectSlot(&WidgetModelTree::onDocumentNameChanged, this); + app->signalDocumentEntityAdded.connectSlot(&WidgetModelTree::onDocumentEntityAdded, this); + app->signalDocumentEntityAboutToBeDestroyed.connectSlot(&WidgetModelTree::onDocumentEntityAboutToBeDestroyed, this); + + m_guiApp->selectionModel()->signalChanged.connectSlot(&WidgetModelTree::onApplicationItemSelectionModelChanged, this); + m_guiApp->signalGuiDocumentAdded.connectSlot([=](GuiDocument* guiDoc) { + guiDoc->signalNodesVisibilityChanged.connectSlot([=](const GuiDocument::MapVisibilityByTreeNodeId& mapNodeId) { this->onNodesVisibilityChanged(guiDoc, mapNodeId); }); }); - QObject::connect( - m_guiApp->selectionModel(), &ApplicationItemSelectionModel::changed, - this, &WidgetModelTree::onApplicationItemSelectionModelChanged); - this->connectTreeWidgetDocumentSelectionChanged(true); } diff --git a/src/app/widget_model_tree_builder_mesh.cpp b/src/app/widget_model_tree_builder_mesh.cpp index d88c0ec8..c6a7f669 100644 --- a/src/app/widget_model_tree_builder_mesh.cpp +++ b/src/app/widget_model_tree_builder_mesh.cpp @@ -6,7 +6,7 @@ #include "widget_model_tree_builder_mesh.h" #include "../base/caf_utils.h" -#include "../base/data_triangulation.h" +#include "../graphics/graphics_mesh_object_driver.h" #include "theme.h" #include "widget_model_tree.h" @@ -16,7 +16,7 @@ namespace Mayo { bool WidgetModelTreeBuilder_Mesh::supportsDocumentTreeNode(const DocumentTreeNode& node) const { - return CafUtils::hasAttribute(node.label()); + return GraphicsMeshObjectDriver::meshSupportStatus(node.label()) == GraphicsObjectDriver::Support::Complete; } QTreeWidgetItem* WidgetModelTreeBuilder_Mesh::createTreeItem(const DocumentTreeNode& node) diff --git a/src/app/widget_model_tree_builder_xde.cpp b/src/app/widget_model_tree_builder_xde.cpp index 7006fbb1..8f7df976 100644 --- a/src/app/widget_model_tree_builder_xde.cpp +++ b/src/app/widget_model_tree_builder_xde.cpp @@ -6,6 +6,7 @@ #include "../base/property_enumeration.h" #include "../base/settings.h" #include "../base/xcaf.h" +#include "../graphics/graphics_shape_object_driver.h" #include "../gui/gui_application.h" #include "app_module.h" #include "qtcore_utils.h" @@ -81,7 +82,7 @@ class WidgetModelTreeBuilder_Xde::Module : public PropertyGroup { bool WidgetModelTreeBuilder_Xde::supportsDocumentTreeNode(const DocumentTreeNode& node) const { - return XCaf::isShape(node.label()); + return GraphicsShapeObjectDriver::shapeSupportStatus(node.label()) == GraphicsObjectDriver::Support::Complete; } void WidgetModelTreeBuilder_Xde::refreshTextTreeItem( diff --git a/src/app/widget_occ_view.cpp b/src/app/widget_occ_view.cpp index a3a62fd6..f907f500 100644 --- a/src/app/widget_occ_view.cpp +++ b/src/app/widget_occ_view.cpp @@ -35,7 +35,7 @@ void IWidgetOccView::setCreator(IWidgetOccView::Creator fn) IWidgetOccView* IWidgetOccView::create(const Handle_V3d_View& view, QWidget* parent) { - auto& fn = getWidgetOccViewCreator(); + const auto& fn = getWidgetOccViewCreator(); return fn(view, parent); } diff --git a/src/app/widget_occ_view.h b/src/app/widget_occ_view.h index a1d83554..0f7f3e8a 100644 --- a/src/app/widget_occ_view.h +++ b/src/app/widget_occ_view.h @@ -62,7 +62,7 @@ class QOpenGLWidgetOccView : public QOpenGLWidget, public IWidgetOccView { }; #endif -// Fallback using traditional QWidget wrapper, no translucid brackground support +// Fallback using traditional QWidget wrapper, no translucid background support class QWidgetOccView : public QWidget, public IWidgetOccView { public: QWidgetOccView(const Handle_V3d_View& view, QWidget* parent = nullptr); diff --git a/src/app/widget_occ_view_controller.cpp b/src/app/widget_occ_view_controller.cpp index 6bf612c2..c3edeaaf 100644 --- a/src/app/widget_occ_view_controller.cpp +++ b/src/app/widget_occ_view_controller.cpp @@ -99,7 +99,8 @@ class RubberBandWidget : public RubberBandWidget_ParentType { } // namespace Internal WidgetOccViewController::WidgetOccViewController(IWidgetOccView* occView) - : V3dViewController(occView->v3dView(), occView->widget()), + : QObject(occView->widget()), + V3dViewController(occView->v3dView()), m_occView(occView), m_navigStyle(NavigationStyle::Catia), m_actionMatcher(createActionMatcher(m_navigStyle, &m_inputSequence)) @@ -168,14 +169,14 @@ void WidgetOccViewController::setViewCursor(const QCursor &cursor) m_occView->widget()->setCursor(cursor); } -struct WidgetOccViewController::RubberBand : public V3dViewController::AbstractRubberBand { +struct WidgetOccViewController::RubberBand : public V3dViewController::IRubberBand { RubberBand(QWidget* parent) : m_rubberBand(parent) { } - void updateGeometry(const QRect& rect) override { - m_rubberBand.setGeometry(rect); + void updateGeometry(int x, int y, int width, int height) override { + m_rubberBand.setGeometry(x, y, width, height); } void setVisible(bool on) override { @@ -186,7 +187,7 @@ struct WidgetOccViewController::RubberBand : public V3dViewController::AbstractR Internal::RubberBandWidget m_rubberBand; }; -std::unique_ptr WidgetOccViewController::createRubberBand() +std::unique_ptr WidgetOccViewController::createRubberBand() { return std::make_unique(m_occView->widget()); } @@ -224,10 +225,10 @@ void WidgetOccViewController::handleKeyPress(const QKeyEvent* event) m_inputSequence.push(event->key()); if (m_inputSequence.equal({ Qt::Key_Space })) - this->startInstantZoom(m_occView->widget()->mapFromGlobal(QCursor::pos())); + this->startInstantZoom(toPosition(m_occView->widget()->mapFromGlobal(QCursor::pos()))); if (m_inputSequence.equal({ Qt::Key_Shift }) && !this->hasCurrentDynamicAction()) - emit this->multiSelectionToggled(true); + this->signalMultiSelectionToggled.send(true); } void WidgetOccViewController::handleKeyRelease(const QKeyEvent* event) @@ -243,20 +244,20 @@ void WidgetOccViewController::handleKeyRelease(const QKeyEvent* event) this->stopInstantZoom(); if (m_inputSequence.lastInput() == Qt::Key_Shift && !this->hasCurrentDynamicAction()) - emit this->multiSelectionToggled(false); + this->signalMultiSelectionToggled.send(false); } void WidgetOccViewController::handleMouseButtonPress(const QMouseEvent* event) { m_inputSequence.push(event->button()); const QPoint currPos = m_occView->widget()->mapFromGlobal(event->globalPos()); - m_prevPos = currPos; + m_prevPos = toPosition(currPos); } void WidgetOccViewController::handleMouseMove(const QMouseEvent* event) { - const QPoint currPos = m_occView->widget()->mapFromGlobal(event->globalPos()); - const QPoint prevPos = m_prevPos; + const Position currPos = toPosition(m_occView->widget()->mapFromGlobal(event->globalPos())); + const Position prevPos = m_prevPos; m_prevPos = currPos; if (m_actionMatcher->matchRotation()) this->rotation(currPos); @@ -267,19 +268,29 @@ void WidgetOccViewController::handleMouseMove(const QMouseEvent* event) else if (m_actionMatcher->matchWindowZoom()) this->windowZoomRubberBand(currPos); else - emit mouseMoved(currPos); + this->signalMouseMoved.send(currPos.x, currPos.y); } void WidgetOccViewController::handleMouseButtonRelease(const QMouseEvent* event) { + auto fnOccMouseBtn = [](Qt::MouseButton btn) -> Aspect_VKeyMouse { + switch (btn) { + case Qt::NoButton: return Aspect_VKeyMouse_NONE; + case Qt::LeftButton: return Aspect_VKeyMouse_LeftButton; + case Qt::RightButton: return Aspect_VKeyMouse_RightButton; + case Qt::MiddleButton: return Aspect_VKeyMouse_MiddleButton; + default: return Aspect_VKeyMouse_UNKNOWN; + } + }; + m_inputSequence.release(event->button()); const bool hadDynamicAction = this->hasCurrentDynamicAction(); if (this->isWindowZoomingStarted()) - this->windowZoom(m_occView->widget()->mapFromGlobal(event->globalPos())); + this->windowZoom(toPosition(m_occView->widget()->mapFromGlobal(event->globalPos()))); this->stopDynamicAction(); if (!hadDynamicAction) - emit mouseClicked(event->button()); + this->signalMouseButtonClicked.send(fnOccMouseBtn(event->button())); } void WidgetOccViewController::handleMouseWheel(const QWheelEvent* event) diff --git a/src/app/widget_occ_view_controller.h b/src/app/widget_occ_view_controller.h index c475b079..7ce388d1 100644 --- a/src/app/widget_occ_view_controller.h +++ b/src/app/widget_occ_view_controller.h @@ -6,9 +6,11 @@ #pragma once -#include "../graphics/v3d_view_controller.h" +#include "../gui/v3d_view_controller.h" #include "../base/span.h" +#include +#include #include #include #include @@ -22,7 +24,7 @@ namespace Mayo { class IWidgetOccView; -class WidgetOccViewController : public V3dViewController { +class WidgetOccViewController : public QObject, public V3dViewController { Q_OBJECT public: WidgetOccViewController(IWidgetOccView* occView = nullptr); @@ -34,19 +36,19 @@ class WidgetOccViewController : public V3dViewController { }; void setNavigationStyle(NavigationStyle style); -signals: - void multiSelectionToggled(bool on); - protected: void redrawView() override; private: + static Position toPosition(const QPoint& pnt) { return { pnt.x(), pnt.y() }; } + static QPoint toQPoint(const Position& pos) { return { pos.x, pos.y }; } + void startDynamicAction(DynamicAction action) override; void stopDynamicAction() override; void setViewCursor(const QCursor& cursor); - std::unique_ptr createRubberBand() override; + std::unique_ptr createRubberBand() override; struct RubberBand; void handleEvent(const QEvent* event); @@ -118,7 +120,7 @@ class WidgetOccViewController : public V3dViewController { // -- Attributes IWidgetOccView* m_occView = nullptr; - QPoint m_prevPos; + Position m_prevPos; NavigationStyle m_navigStyle = NavigationStyle::Mayo; InputSequence m_inputSequence; std::unique_ptr m_actionMatcher; diff --git a/src/app/widget_occ_view_impl.cpp b/src/app/widget_occ_view_impl.cpp index d4deaa9a..d3f33345 100644 --- a/src/app/widget_occ_view_impl.cpp +++ b/src/app/widget_occ_view_impl.cpp @@ -51,6 +51,7 @@ class QtOccFrameBuffer : public OpenGl_FrameBuffer { { OpenGl_FrameBuffer::BindBuffer(ctx); ctx->SetFrameBufferSRGB(true, false); + // NOTE: commenting the line just above makes the FBO to work on some configs(eg VM Ubuntu 18.04) } void BindDrawBuffer(const Handle(OpenGl_Context)& ctx) override @@ -93,8 +94,10 @@ Handle_Graphic3d_GraphicDriver QOpenGLWidgetOccView_createCompatibleGraphicsDriv // Offscreen FBOs should be always used gfxDriver->ChangeOptions().useSystemBuffer = false; #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) - Message::SendWarning("Warning! Qt 5.10+ is required for sRGB setup.\n" - "Colors in 3D Viewer might look incorrect (Qt " QT_VERSION_STR " is used).\n"); + Message::SendWarning( + "Warning! Qt 5.10+ is required for sRGB setup.\n" + "Colors in 3D Viewer might look incorrect (Qt " QT_VERSION_STR " is used).\n" + ); gfxDriver->ChangeOptions().sRGBDisable = true; #endif @@ -117,9 +120,9 @@ bool QOpenGLWidgetOccView_wrapFrameBuffer(const Handle_Graphic3d_GraphicDriver& } if (!defaultFbo->InitWrapper(glCtx)) { - defaultFbo.Nullify(); - Message::SendFail() << "Default FBO wrapper creation failed"; - return false; + defaultFbo.Nullify(); + Message::SendFail() << "Default FBO wrapper creation failed"; + return false; } return true; diff --git a/src/app/widget_properties_editor.cpp b/src/app/widget_properties_editor.cpp index 758557d7..72fe5038 100644 --- a/src/app/widget_properties_editor.cpp +++ b/src/app/widget_properties_editor.cpp @@ -5,6 +5,7 @@ ****************************************************************************/ #include "widget_properties_editor.h" +#include "qmeta_property.h" #include "qstring_conv.h" #include "ui_widget_properties_editor.h" diff --git a/src/app/windows/win_taskbar_global_progress.cpp b/src/app/windows/win_taskbar_global_progress.cpp index 397e739f..d13a6b52 100644 --- a/src/app/windows/win_taskbar_global_progress.cpp +++ b/src/app/windows/win_taskbar_global_progress.cpp @@ -14,20 +14,13 @@ namespace Mayo { -WinTaskbarGlobalProgress::WinTaskbarGlobalProgress(const TaskManager* taskMgr, QObject* parent) +WinTaskbarGlobalProgress::WinTaskbarGlobalProgress(TaskManager* taskMgr, QObject* parent) : QObject(parent), - m_taskbarBtn(new QWinTaskbarButton(this)), - m_taskMgr(taskMgr) + m_taskbarBtn(new QWinTaskbarButton(this)) { - QObject::connect( - taskMgr, &TaskManager::started, - this, [=](TaskId taskId) { this->onTaskProgress(taskId, 0); }); - QObject::connect( - taskMgr, &TaskManager::progressChanged, - this, &WinTaskbarGlobalProgress::onTaskProgress); - QObject::connect( - taskMgr, &TaskManager::ended, - this, &WinTaskbarGlobalProgress::onTaskEnded); + taskMgr->signalStarted.connectSlot([=](TaskId taskId) { this->onTaskProgress(taskId, 0); }); + taskMgr->signalProgressChanged.connectSlot(&WinTaskbarGlobalProgress::onTaskProgress, this); + taskMgr->signalEnded.connectSlot(&WinTaskbarGlobalProgress::onTaskEnded, this); } void WinTaskbarGlobalProgress::setWindow(QWindow* window) @@ -78,7 +71,7 @@ void WinTaskbarGlobalProgress::updateTaskbar() taskbarProgress->show(); taskbarProgress->resume(); if (!isProgressIndeterminate) { - const int newGlobalPct = MathUtils::mappedValue(taskAccumPct, 0, taskCount * 100, 0, 100); + const int newGlobalPct = MathUtils::toPercent(taskAccumPct, 0, taskCount * 100); m_globalPct = std::max(newGlobalPct, m_globalPct); taskbarProgress->setRange(0, 100); taskbarProgress->setValue(m_globalPct); diff --git a/src/app/windows/win_taskbar_global_progress.h b/src/app/windows/win_taskbar_global_progress.h index d185f126..e5831ba9 100644 --- a/src/app/windows/win_taskbar_global_progress.h +++ b/src/app/windows/win_taskbar_global_progress.h @@ -22,7 +22,7 @@ class TaskManager; class WinTaskbarGlobalProgress : public QObject { Q_OBJECT public: - WinTaskbarGlobalProgress(const TaskManager* taskMgr, QObject* parent = nullptr); + WinTaskbarGlobalProgress(TaskManager* taskMgr, QObject* parent = nullptr); void setWindow(QWindow* window); @@ -31,7 +31,6 @@ class WinTaskbarGlobalProgress : public QObject { void onTaskEnded(TaskId taskId); void updateTaskbar(); - const TaskManager* m_taskMgr = nullptr; std::unordered_map m_mapTaskIdProgress; QWinTaskbarButton* m_taskbarBtn = nullptr; int m_globalPct = 0; diff --git a/src/base/application.cpp b/src/base/application.cpp index d2fd1ee8..8df0ca90 100644 --- a/src/base/application.cpp +++ b/src/base/application.cpp @@ -55,6 +55,10 @@ struct Application::Private { std::vector m_vecTranslator; }; +struct ApplicationI18N { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::Application) +}; + Application::~Application() { delete d; @@ -63,25 +67,19 @@ Application::~Application() const ApplicationPtr& Application::instance() { static ApplicationPtr appPtr; - if (appPtr.IsNull()) { + if (!appPtr) { appPtr = new Application; const char strFougueCopyright[] = "Copyright (c) 2021, Fougue Ltd. "; appPtr->DefineFormat( - Document::NameFormatBinary, qUtf8Printable(tr("Binary Mayo Document Format")), "myb", + Document::NameFormatBinary, ApplicationI18N::textIdTr("Binary Mayo Document Format").data(), "myb", new Document::FormatBinaryRetrievalDriver(appPtr), - new BinXCAFDrivers_DocumentStorageDriver); + new BinXCAFDrivers_DocumentStorageDriver + ); appPtr->DefineFormat( - Document::NameFormatXml, qUtf8Printable(tr("XML Mayo Document Format")), "myx", + Document::NameFormatXml, ApplicationI18N::textIdTr("XML Mayo Document Format").data(), "myx", new Document::FormatXmlRetrievalDriver(appPtr), - new XmlXCAFDrivers_DocumentStorageDriver(strFougueCopyright)); - - qRegisterMetaType("Mayo::TreeNodeId"); - qRegisterMetaType("TreeNodeId"); - qRegisterMetaType("Mayo::DocumentPtr"); - qRegisterMetaType("DocumentPtr"); - qRegisterMetaType("Mayo::TaskId"); - qRegisterMetaType("TaskId"); - qRegisterMetaType("std::string"); + new XmlXCAFDrivers_DocumentStorageDriver(strFougueCopyright) + ); } return appPtr; @@ -153,6 +151,11 @@ int Application::findIndexOfDocument(const DocumentPtr& doc) const void Application::closeDocument(const DocumentPtr& doc) { TDocStd_Application::Close(doc); + doc->signalNameChanged.disconnectAll(); + doc->signalFilePathChanged.disconnectAll(); + doc->signalEntityAdded.disconnectAll(); + doc->signalEntityAboutToBeDestroyed.disconnectAll(); + //doc->Main().ForgetAllAttributes(true/*clearChildren*/); } void Application::addTranslator(Application::Translator fn) @@ -231,8 +234,7 @@ void Application::InitDocument(const Handle(TDocStd_Document)& doc) const } Application::Application() - : QObject(nullptr), - d(new Private) + : d(new Private) { } @@ -240,7 +242,7 @@ void Application::notifyDocumentAboutToClose(Document::Identifier docIdent) { auto itFound = d->m_mapIdentifierDocument.find(docIdent); if (itFound != d->m_mapIdentifierDocument.end()) { - emit this->documentAboutToClose(itFound->second); + this->signalDocumentAboutToClose.send(itFound->second); d->m_mapIdentifierDocument.erase(itFound); } } @@ -253,19 +255,19 @@ void Application::addDocument(const DocumentPtr& doc) this->InitDocument(doc); doc->initXCaf(); - QObject::connect( - doc.get(), &Document::nameChanged, - this, [=](const std::string& name) { emit this->documentNameChanged(doc, name); }); - QObject::connect( - doc.get(), &Document::entityAdded, - this, [=](TreeNodeId entityId) { emit this->documentEntityAdded(doc, entityId); }); - QObject::connect( - doc.get(), &Document::entityAboutToBeDestroyed, - this, [=](TreeNodeId entityId) { emit this->documentEntityAboutToBeDestroyed(doc, entityId); }); -// QObject::connect( -// doc, &Document::itemPropertyChanged, -// this, &Application::documentItemPropertyChanged); - emit documentAdded(doc); + doc->signalNameChanged.connectSlot([=](const std::string& name) { + this->signalDocumentNameChanged.send(doc, name); + }); + doc->signalFilePathChanged.connectSlot([=](const FilePath& fp) { + this->signalDocumentFilePathChanged.send(doc, fp); + }); + doc->signalEntityAdded.connectSlot([=](TreeNodeId entityId) { + this->signalDocumentEntityAdded.send(doc, entityId); + }); + doc->signalEntityAboutToBeDestroyed.connectSlot([=](TreeNodeId entityId) { + this->signalDocumentEntityAboutToBeDestroyed.send(doc, entityId); + }); + this->signalDocumentAdded.send(doc); } } diff --git a/src/base/application.h b/src/base/application.h index 2f09f002..e685d38a 100644 --- a/src/base/application.h +++ b/src/base/application.h @@ -8,6 +8,7 @@ #include "application_ptr.h" #include "document.h" +#include "signal.h" #include "span.h" #include "text_id.h" @@ -18,8 +19,7 @@ namespace Mayo { // Provides management of Document objects -class Application : public QObject, public TDocStd_Application { - Q_OBJECT +class Application : public TDocStd_Application { public: ~Application(); @@ -59,6 +59,14 @@ class Application : public QObject, public TDocStd_Application { static Span envOpenCascadeOptions(); static Span envOpenCascadePaths(); + // Signals + Signal signalDocumentAdded; + Signal signalDocumentAboutToClose; + Signal signalDocumentNameChanged; + Signal signalDocumentFilePathChanged; + Signal signalDocumentEntityAdded; + Signal signalDocumentEntityAboutToBeDestroyed; + public: // -- from TDocStd_Application #if OCC_VERSION_HEX >= 0x070600 void NewDocument(const TCollection_ExtendedString& format, Handle(CDM_Document)& outDoc) override; @@ -76,13 +84,6 @@ class Application : public QObject, public TDocStd_Application { DEFINE_STANDARD_RTTI_INLINE(Application, TDocStd_Application) -signals: - void documentAdded(const Mayo::DocumentPtr& doc); - void documentAboutToClose(const Mayo::DocumentPtr& doc); - void documentNameChanged(const Mayo::DocumentPtr& doc, const std::string& name); - void documentEntityAdded(const Mayo::DocumentPtr& doc, Mayo::TreeNodeId entityId); - void documentEntityAboutToBeDestroyed(const Mayo::DocumentPtr& doc, Mayo::TreeNodeId entityId); - private: // Implementation friend class Document; @@ -91,7 +92,7 @@ class Application : public QObject, public TDocStd_Application { void addDocument(const DocumentPtr& doc); struct Private; - Private* const d; + Private* const d = nullptr; }; } // namespace Mayo diff --git a/src/base/application_item_selection_model.cpp b/src/base/application_item_selection_model.cpp index 06517900..6afeb345 100644 --- a/src/base/application_item_selection_model.cpp +++ b/src/base/application_item_selection_model.cpp @@ -24,11 +24,6 @@ static std::vector::iterator findApplicationItem( } // namespace Internal -ApplicationItemSelectionModel::ApplicationItemSelectionModel(QObject* parent) - : QObject(parent) -{ -} - Span ApplicationItemSelectionModel::selectedItems() const { return m_vecSelectedItem; @@ -44,7 +39,7 @@ void ApplicationItemSelectionModel::add(const ApplicationItem& item) if (!Internal::hasApplicationItem(m_vecSelectedItem, item)) { m_vecSelectedItem.push_back(item); std::vector vecItem = { item }; - emit changed(vecItem, {}); + this->signalChanged.send(vecItem, {}); } } @@ -59,7 +54,7 @@ void ApplicationItemSelectionModel::add(Span vecItem) } if (!signalVecItem.empty()) - emit changed(signalVecItem, {}); + this->signalChanged.send(signalVecItem, {}); } void ApplicationItemSelectionModel::remove(const ApplicationItem& item) @@ -68,7 +63,7 @@ void ApplicationItemSelectionModel::remove(const ApplicationItem& item) if (itFound != m_vecSelectedItem.end()) { m_vecSelectedItem.erase(itFound); std::vector vecItem = { item }; - emit changed({}, vecItem); + this->signalChanged.send({}, vecItem); } } @@ -84,7 +79,7 @@ void ApplicationItemSelectionModel::remove(Span vecItem) } if (!signalVecItem.empty()) - emit changed({}, signalVecItem); + this->signalChanged.send({}, signalVecItem); } void ApplicationItemSelectionModel::clear() @@ -93,7 +88,7 @@ void ApplicationItemSelectionModel::clear() // Warning: slots connected to changed() signal may indirectly access m_vecSelectedItem const auto vecDeselectedItem = m_vecSelectedItem; m_vecSelectedItem.clear(); - emit changed({}, vecDeselectedItem); + this->signalChanged.send({}, vecDeselectedItem); } } diff --git a/src/base/application_item_selection_model.h b/src/base/application_item_selection_model.h index 7de4c454..bfd7863b 100644 --- a/src/base/application_item_selection_model.h +++ b/src/base/application_item_selection_model.h @@ -7,16 +7,13 @@ #pragma once #include "application_item.h" +#include "signal.h" #include "span.h" -#include namespace Mayo { -class ApplicationItemSelectionModel : public QObject { - Q_OBJECT +class ApplicationItemSelectionModel { public: - ApplicationItemSelectionModel(QObject* parent = nullptr); - Span selectedItems() const; bool isSelected(const ApplicationItem& item); @@ -30,8 +27,7 @@ class ApplicationItemSelectionModel : public QObject { void clear(); -signals: - void changed(Span selected, Span deselected); + Signal, Span> signalChanged; private: std::vector m_vecSelectedItem; diff --git a/src/base/brep_utils.cpp b/src/base/brep_utils.cpp index 26b4bb86..66e54493 100644 --- a/src/base/brep_utils.cpp +++ b/src/base/brep_utils.cpp @@ -14,12 +14,31 @@ #include #include +#include #include +#include #include #include namespace Mayo { +TopoDS_Compound BRepUtils::makeEmptyCompound() +{ + BRep_Builder builder; + TopoDS_Compound comp; + builder.MakeCompound(comp); + return comp; +} + +TopoDS_Face BRepUtils::makeFace(const Handle(Poly_Triangulation)& mesh) +{ + TopoDS_Face face; + BRep_Builder builder; + builder.MakeFace(face); + builder.UpdateFace(face, mesh); + return face; +} + bool BRepUtils::moreComplex(TopAbs_ShapeEnum lhs, TopAbs_ShapeEnum rhs) { return lhs < rhs; @@ -46,6 +65,16 @@ TopoDS_Shape BRepUtils::shapeFromString(const std::string& str) return shape; } +bool BRepUtils::isGeometric(const TopoDS_Face& face) +{ +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) + return BRep_Tool::IsGeometric(face); +#else + auto tface = static_cast(face.TShape().get()); + return !tface->Surface().IsNull(); +#endif +} + void BRepUtils::computeMesh( const TopoDS_Shape& shape, const OccBRepMeshParameters& params, TaskProgress* progress) { diff --git a/src/base/brep_utils.h b/src/base/brep_utils.h index 99db9399..3a9d6eef 100644 --- a/src/base/brep_utils.h +++ b/src/base/brep_utils.h @@ -8,6 +8,7 @@ #include "occ_brep_mesh_parameters.h" +#include #include #include #include @@ -19,17 +20,23 @@ class TaskProgress; // Provides helper functions for OpenCascade TKBRep library struct BRepUtils { + // Creates a valid and empty TopoDS_Compound shape + static TopoDS_Compound makeEmptyCompound(); + + // Creates a non-geometric TopoDS_Face wrapping triangulation 'mesh' + static TopoDS_Face makeFace(const Handle(Poly_Triangulation)& mesh); + // Iterates with 'explorer' and executes 'fn' for each sub-shape - template - static void forEachSubShape(TopExp_Explorer& explorer, FUNC fn); + template + static void forEachSubShape(TopExp_Explorer& explorer, Function fn); // Explores 'shape' and executes 'fn' for each sub-shape of type 'shapeType' - template - static void forEachSubShape(const TopoDS_Shape& shape, TopAbs_ShapeEnum shapeType, FUNC fn); + template + static void forEachSubShape(const TopoDS_Shape& shape, TopAbs_ShapeEnum shapeType, Function fn); // Explores 'shape' and executes 'fn' for each sub-face - template - static void forEachSubFace(const TopoDS_Shape& shape, FUNC fn); + template + static void forEachSubFace(const TopoDS_Shape& shape, Function fn); // Is shape type 'lhs' more complex than 'rhs'? // Complexity here is the degree of abstraction provided(eg face type is more complex than edge type) @@ -37,7 +44,7 @@ struct BRepUtils { // Returns hash code computed from 'shape' // Computation uses the internal TShape and Location, but Orientation is not considered - // Returnd hash code is in the range [1, max(int)] + // Returned hash code is in the range [1, max(int)] static int hashCode(const TopoDS_Shape& shape); // Serializes 'shape' into a string representation @@ -46,11 +53,15 @@ struct BRepUtils { // Deserializes string 'str' obtained from 'shapeToToString()' into a shape object static TopoDS_Shape shapeFromString(const std::string& str); + // Does 'face' rely on a geometric surface? + static bool isGeometric(const TopoDS_Face& face); + // Computes a mesh representation of 'shape' using OpenCascade meshing algorithm static void computeMesh( const TopoDS_Shape& shape, const OccBRepMeshParameters& params, - TaskProgress* progress = nullptr); + TaskProgress* progress = nullptr + ); }; @@ -59,8 +70,8 @@ struct BRepUtils { // -- Implementation // -- -template -void BRepUtils::forEachSubShape(TopExp_Explorer& explorer, FUNC fn) +template +void BRepUtils::forEachSubShape(TopExp_Explorer& explorer, Function fn) { while (explorer.More()) { fn(explorer.Current()); @@ -68,15 +79,15 @@ void BRepUtils::forEachSubShape(TopExp_Explorer& explorer, FUNC fn) } } -template -void BRepUtils::forEachSubShape(const TopoDS_Shape& shape, TopAbs_ShapeEnum shapeType, FUNC fn) +template +void BRepUtils::forEachSubShape(const TopoDS_Shape& shape, TopAbs_ShapeEnum shapeType, Function fn) { TopExp_Explorer expl(shape, shapeType); BRepUtils::forEachSubShape(expl, std::move(fn)); } -template -void BRepUtils::forEachSubFace(const TopoDS_Shape& shape, FUNC fn) +template +void BRepUtils::forEachSubFace(const TopoDS_Shape& shape, Function fn) { for (TopExp_Explorer expl(shape, TopAbs_FACE); expl.More(); expl.Next()) fn(TopoDS::Face(expl.Current())); diff --git a/src/base/caf_utils.h b/src/base/caf_utils.h index de57afe0..d849de53 100644 --- a/src/base/caf_utils.h +++ b/src/base/caf_utils.h @@ -26,15 +26,15 @@ struct CafUtils { // Is 'label' null or empty(ie no attributes)? static bool isNullOrEmpty(const TDF_Label& label); - // Returns attribute of type 'TDF_ATTRIBUTE'(result may be null)? - template - static opencascade::handle findAttribute(const TDF_Label& label); + // Returns attribute of type 'AttributeType'(result may be null)? + template + static opencascade::handle findAttribute(const TDF_Label& label); // Is there an attribute of identifier 'attrGuid' attached to 'label'? static bool hasAttribute(const TDF_Label& label, const Standard_GUID& attrGuid); - // Is there an attribute of type 'TDF_ATTRIBUTE' attached to 'label'? - template static bool hasAttribute(const TDF_Label& label); + // Is there an attribute of type 'AttributeType' attached to 'label'? + template static bool hasAttribute(const TDF_Label& label); // Returns a TDF_LabelSequence object built from initializer list static TDF_LabelSequence makeLabelSequence(std::initializer_list listLabel); @@ -62,18 +62,18 @@ template<> struct hash { namespace Mayo { -template -opencascade::handle CafUtils::findAttribute(const TDF_Label& label) +template +opencascade::handle CafUtils::findAttribute(const TDF_Label& label) { - opencascade::handle attr; - label.FindAttribute(TDF_ATTRIBUTE::GetID(), attr); + opencascade::handle attr; + label.FindAttribute(AttributeType::GetID(), attr); return attr; } -template +template bool CafUtils::hasAttribute(const TDF_Label& label) { - return hasAttribute(label, TDF_ATTRIBUTE::GetID()); + return hasAttribute(label, AttributeType::GetID()); } } // namespace Mayo diff --git a/src/base/cpp_utils.h b/src/base/cpp_utils.h index 0ffa8dc8..e0ba1d0b 100644 --- a/src/base/cpp_utils.h +++ b/src/base/cpp_utils.h @@ -26,10 +26,11 @@ inline const std::string& nullString() return str; } -template -VALUE findValue(const KEY& key, const std::unordered_map& hashmap) { +template +ValueType findValue(const KeyType& key, const std::unordered_map& hashmap) +{ auto it = hashmap.find(key); - const VALUE defaultValue = {}; + const ValueType defaultValue = {}; return it != hashmap.cend() ? it->second : defaultValue; } @@ -124,10 +125,10 @@ template constexpr bool inRange(T t) noexcept } // Throws object of specified error type if 'condition' is met -template void throwErrorIf(bool condition, ERROR_ARGS... args) +template void throwErrorIf(bool condition, ErrorArgs... args) { if (condition) { - throw ERROR(args...); + throw ErrorType(args...); } } diff --git a/src/base/data_triangulation.cpp b/src/base/data_triangulation.cpp deleted file mode 100644 index fb76d17b..00000000 --- a/src/base/data_triangulation.cpp +++ /dev/null @@ -1,87 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2022, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#include "data_triangulation.h" - -#include -#include -#include -#include - -namespace Mayo { - -const Standard_GUID& DataTriangulation::GetID() -{ - static const Standard_GUID DataTriangulationID("3020FF98-2B49-4E0B-A414-949A534F24F7"); - return DataTriangulationID; -} - -DataTriangulationPtr DataTriangulation::Set(const TDF_Label& label) -{ - DataTriangulationPtr dataMesh; - if (!label.FindAttribute(DataTriangulation::GetID(), dataMesh)) { - dataMesh = new DataTriangulation; - label.AddAttribute(dataMesh); - } - - return dataMesh; -} - -DataTriangulationPtr DataTriangulation::Set(const TDF_Label& label, const Handle(Poly_Triangulation)& mesh) -{ - DataTriangulationPtr dataMesh = DataTriangulation::Set(label); - dataMesh->TDataXtd_Triangulation::Set(mesh); - return dataMesh; -} - -DataTriangulationPtr DataTriangulation::Set( - const TDF_Label& label, const Handle(Poly_Triangulation)& mesh, Span spanNodeColor) -{ - DataTriangulationPtr dataMesh = DataTriangulation::Set(label, mesh); - dataMesh->copyNodeColors(spanNodeColor); - return dataMesh; -} - -const Standard_GUID& DataTriangulation::ID() const -{ - return DataTriangulation::GetID(); -} - -void DataTriangulation::Restore(const Handle(TDF_Attribute)& attribute) -{ - TDataXtd_Triangulation::Restore(attribute); - auto dataMesh = DataTriangulationPtr::DownCast(attribute); - if (dataMesh) - this->copyNodeColors(dataMesh->m_vecNodeColor); -} - -Handle(TDF_Attribute) DataTriangulation::NewEmpty() const -{ - return new DataTriangulation; -} - -void DataTriangulation::Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)& table) const -{ - TDataXtd_Triangulation::Paste(into, table); - auto dataMesh = DataTriangulationPtr::DownCast(into); - if (dataMesh) - dataMesh->copyNodeColors(m_vecNodeColor); -} - -Standard_OStream& DataTriangulation::Dump(Standard_OStream& ostr) const -{ - ostr << "DataTriangulation -- "; - TDataXtd_Triangulation::Dump(ostr); - return ostr; -} - -void Mayo::DataTriangulation::copyNodeColors(Span spanNodeColor) -{ - m_vecNodeColor.clear(); - std::copy(spanNodeColor.begin(), spanNodeColor.end(), std::back_inserter(m_vecNodeColor)); -} - -} // namespace Mayo diff --git a/src/base/document.cpp b/src/base/document.cpp index aee14dd2..915e21cd 100644 --- a/src/base/document.cpp +++ b/src/base/document.cpp @@ -16,13 +16,16 @@ namespace Mayo { Document::Document(const ApplicationPtr& app) - : QObject(app.get()), - TDocStd_Document(NameFormatBinary), + : TDocStd_Document(NameFormatBinary), m_app(app) { TDF_TagSource::Set(this->rootLabel()); } +Document::~Document() +{ +} + void Document::initXCaf() { m_xcaf.setLabelMain(this->Main()); @@ -37,7 +40,7 @@ const std::string& Document::name() const void Document::setName(std::string_view name) { m_name = name; - emit this->nameChanged(m_name); + this->signalNameChanged.send(m_name); } const FilePath& Document::filePath() const @@ -48,6 +51,7 @@ const FilePath& Document::filePath() const void Document::setFilePath(const FilePath& fp) { m_filePath = fp; + this->signalFilePathChanged.send(fp); } const char* Document::toNameFormat(Document::Format format) @@ -134,6 +138,11 @@ TDF_Label Document::newEntityLabel() return this->rootLabel().NewChild(); } +TDF_Label Document::newEntityShapeLabel() +{ + return m_xcaf.shapeTool()->NewShape(); +} + void Document::addEntityTreeNode(const TDF_Label& label) { // Check if 'label' belongs to current document @@ -148,7 +157,7 @@ void Document::addEntityTreeNode(const TDF_Label& label) // TODO Allow custom population of the model tree for the new entity const TreeNodeId nodeId = m_xcaf.deepBuildAssemblyTree(0, label); - emit this->entityAdded(nodeId); + this->signalEntityAdded.send(nodeId); #if 0 // Remove 'label' @@ -165,7 +174,7 @@ void Document::destroyEntity(TreeNodeId entityTreeNodeId) if (CafUtils::isNullOrEmpty(entityLabel)) return; - emit this->entityAboutToBeDestroyed(entityTreeNodeId); + this->signalEntityAboutToBeDestroyed.send(entityTreeNodeId); entityLabel.ForgetAllAttributes(); entityLabel.Nullify(); m_modelTree.removeRoot(entityTreeNodeId); diff --git a/src/base/document.h b/src/base/document.h index 52d554ee..8869af6c 100644 --- a/src/base/document.h +++ b/src/base/document.h @@ -11,22 +11,17 @@ #include "document_tree_node.h" #include "filepath.h" #include "libtree.h" +#include "signal.h" #include "xcaf.h" -#include #include #include namespace Mayo { -class Document : public QObject, public TDocStd_Document { - Q_OBJECT - Q_PROPERTY(int identifier READ identifier) - Q_PROPERTY(std::string name READ name WRITE setName NOTIFY nameChanged) - Q_PROPERTY(FilePath filePath READ filePath WRITE setFilePath) - Q_PROPERTY(bool isXCafDocument READ isXCafDocument) +class Document : public TDocStd_Document { public: - using Identifier = int; + using Identifier = int; // TODO alias TypedScalar enum class Format { Binary, Xml }; Identifier identifier() const { return m_identifier; } @@ -60,15 +55,20 @@ class Document : public QObject, public TDocStd_Document { static DocumentPtr findFrom(const TDF_Label& label); + // Creates general-purpose entity, not bound to a specific type TDF_Label newEntityLabel(); + + // Creates entity bound to a BRep shape and registered as top-level into XCAFDoc_ShapeTool + TDF_Label newEntityShapeLabel(); + void addEntityTreeNode(const TDF_Label& label); void destroyEntity(TreeNodeId entityTreeNodeId); -signals: - void nameChanged(const std::string& name); - void entityAdded(Mayo::TreeNodeId entityTreeNodeId); - void entityAboutToBeDestroyed(Mayo::TreeNodeId entityTreeNodeId); - //void itemPropertyChanged(DocumentItem* docItem, Property* prop); + // Signals + Signal signalNameChanged; + Signal signalFilePathChanged; + Signal signalEntityAdded; + Signal signalEntityAboutToBeDestroyed; public: // -- from TDocStd_Document void BeforeClose() override; @@ -78,6 +78,7 @@ class Document : public QObject, public TDocStd_Document { private: Document(const ApplicationPtr& app); + ~Document(); friend class Application; class FormatBinaryRetrievalDriver; diff --git a/src/base/document_ptr.h b/src/base/document_ptr.h index ff8c446a..0e2c4011 100644 --- a/src/base/document_ptr.h +++ b/src/base/document_ptr.h @@ -12,6 +12,6 @@ namespace Mayo { class Document; DEFINE_STANDARD_HANDLE(Document, TDocStd_Document) -using DocumentPtr = opencascade::handle; +using DocumentPtr = Handle(Document); } // namespace Mayo diff --git a/src/base/enumeration.h b/src/base/enumeration.h index 86e81e20..61c73776 100644 --- a/src/base/enumeration.h +++ b/src/base/enumeration.h @@ -31,8 +31,8 @@ class Enumeration { Enumeration(std::initializer_list listItem); // Adds an enumerated item - template - Enumeration& addItem(VALUE value, const TextId& name); + template + Enumeration& addItem(ValueType value, const TextId& name); // Iterates over name of items and removes 'prefix' string that may appear at the beginning Enumeration& chopPrefix(std::string_view prefix); @@ -45,16 +45,16 @@ class Enumeration { bool empty() const { return m_vecItem.empty(); } // Finds index of the item corresponding to an enumerated value. Returns -1 if not found - template int findIndexByValue(ENUM value) const; + template int findIndexByValue(EnumType value) const; // Finds the item corresponding to an enumerated value. Returns nullptr if not found - template const Item* findItemByValue(ENUM value) const; + template const Item* findItemByValue(EnumType value) const; // Finds the item corresponding to a name. Returns nullptr if not found const Item* findItemByName(std::string_view name) const; // Finds the name of an enumerated value. Returns empty string if not found - template std::string_view findNameByValue(ENUM value) const; + template std::string_view findNameByValue(EnumType value) const; // Finds the enumerated value corresponding to a name. Throws exception if not found Value findValueByName(std::string_view name) const; @@ -70,7 +70,7 @@ class Enumeration { // Creates an Enumeration object from an enumerated type, using MetaEnum helper // Note: client code has to include header "enumeration_fromenum.h" - template + template static Enumeration fromType(); private: @@ -83,27 +83,27 @@ class Enumeration { // -- Implementation -template Enumeration& Enumeration::addItem(VALUE value, const TextId& name) +template Enumeration& Enumeration::addItem(ValueType value, const TextId& name) { const Item item = { Enumeration::Value(value), name }; m_vecItem.emplace_back(std::move(item)); return *this; } -template int Enumeration::findIndexByValue(ENUM value) const +template int Enumeration::findIndexByValue(EnumType value) const { - static_assert(std::is_enum_v || std::is_integral_v, "ENUM must be an enumeration or integer type"); + static_assert(std::is_enum_v || std::is_integral_v, "ENUM must be an enumeration or integer type"); return this->findIndexByValue_untyped(static_cast(value)); } -template const Enumeration::Item* Enumeration::findItemByValue(ENUM value) const +template const Enumeration::Item* Enumeration::findItemByValue(EnumType value) const { - static_assert(std::is_enum_v || std::is_integral_v, "ENUM must be an enumeration or integer type"); + static_assert(std::is_enum_v || std::is_integral_v, "EnumType must be an enumeration or integer type"); const int index = this->findIndexByValue_untyped(static_cast(value)); return index != -1 ? &(this->itemAt(index)) : nullptr; } -template std::string_view Enumeration::findNameByValue(ENUM value) const +template std::string_view Enumeration::findNameByValue(EnumType value) const { const int index = this->findIndexByValue(value); if (index != -1) diff --git a/src/base/enumeration_fromenum.h b/src/base/enumeration_fromenum.h index 01d91c26..87382eaa 100644 --- a/src/base/enumeration_fromenum.h +++ b/src/base/enumeration_fromenum.h @@ -15,23 +15,23 @@ namespace Mayo { -template +template struct EnumNames { static inline const std::string_view trContext = ""; static inline const std::string_view junkPrefix = ""; }; -template +template Enumeration Enumeration::fromType() { - const bool hasJunkPrefix = !EnumNames::junkPrefix.empty(); + const bool hasJunkPrefix = !EnumNames::junkPrefix.empty(); Enumeration enumObject; - for (const ENUM value : MetaEnum::values()) { + for (const EnumType value : MetaEnum::values()) { std::string_view key = hasJunkPrefix ? - MetaEnum::nameWithoutPrefix(value, EnumNames::junkPrefix) : - MetaEnum::name(value); - const TextId keyTextId = { EnumNames::trContext, key }; + MetaEnum::nameWithoutPrefix(value, EnumNames::junkPrefix) : + MetaEnum::name(value); + const TextId keyTextId = { EnumNames::trContext, key }; enumObject.addItem(int(value), keyTextId); } diff --git a/src/base/filepath.h b/src/base/filepath.h index 0e2803cc..821ef524 100644 --- a/src/base/filepath.h +++ b/src/base/filepath.h @@ -6,25 +6,41 @@ #pragma once -// Workaround bug https://bugreports.qt.io/browse/QTBUG-73263 which affects GCC builds -#ifndef Q_MOC_RUN -# include +#if defined(__cpp_lib_filesystem) +# define MAYO_HAS_STD_FILESYSTEM +#elif defined(__has_include) +# if __has_include() +# define MAYO_HAS_STD_FILESYSTEM +# endif +#endif + +#ifndef Q_MOC_RUN // Workaround bug https://bugreports.qt.io/browse/QTBUG-73263 which affects GCC builds +# ifdef MAYO_HAS_STD_FILESYSTEM +# include +namespace std_filesystem = std::filesystem; +# else +# include +namespace std_filesystem = std::experimental::filesystem; +# endif #endif namespace Mayo { -using FilePath = std::filesystem::path; +using FilePath = std_filesystem::path; // Exception-safe version of std::filesystem::file_size() inline uintmax_t filepathFileSize(const FilePath& fp) { - std::error_code ec; - return std::filesystem::file_size(fp, ec); + try { + return std_filesystem::file_size(fp); + } catch (...) { // fs::file_size() might throw on non-existing files + return 0; + } } // Exception-safe version of std::filesystem::canonical() inline FilePath filepathCanonical(const FilePath& fp) { try { - return std::filesystem::canonical(fp); + return std_filesystem::canonical(fp); } catch (...) { // fs::canonical() might throw on non-existing files return fp; } @@ -33,7 +49,7 @@ inline FilePath filepathCanonical(const FilePath& fp) { // Exception-safe version of std::filesystem::equivalent() inline bool filepathExists(const FilePath& fp) { try { - return std::filesystem::exists(fp); + return std_filesystem::exists(fp); } catch (...) { // fs::exists() might throw on non-existing files return false; } @@ -45,7 +61,7 @@ inline bool filepathEquivalent(const FilePath& lhs, const FilePath& rhs) { if (lhs == rhs) return true; else - return std::filesystem::equivalent(lhs, rhs); + return std_filesystem::equivalent(lhs, rhs); } catch (...) { // fs::equivalent() might throw on non-existing files return false; } @@ -54,10 +70,19 @@ inline bool filepathEquivalent(const FilePath& lhs, const FilePath& rhs) { // Exception-safe version of std::filesystem::is_regular_file() inline bool filepathIsRegularFile(const FilePath& fp) { try { - return std::filesystem::is_regular_file(fp); + return std_filesystem::is_regular_file(fp); } catch (...) { return false; } } +// Exception-safe version of std::filesystem::last_write_time() +inline std_filesystem::file_time_type filepathLastWriteTime(const FilePath& fp) { + try { + return std_filesystem::last_write_time(fp); + } catch (...) { + return {}; + } +} + } // namespace Mayo diff --git a/src/base/filepath_conv.h b/src/base/filepath_conv.h index f2460318..28767fde 100644 --- a/src/base/filepath_conv.h +++ b/src/base/filepath_conv.h @@ -7,9 +7,11 @@ #pragma once #include "filepath.h" +#include "global.h" #include #include +#include namespace Mayo { @@ -33,7 +35,7 @@ template<> struct FilePathConv { static auto to(const FilePath& fp) { // Can't use "if constexpr(std::is_same_v)" here because // discarded statements are compiled. On Windows compilation would fail. -#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +#ifdef MAYO_OS_WINDOWS // Windows -> FilePath::value_type is "char" return TCollection_ExtendedString(fp.c_str()); #else @@ -43,4 +45,11 @@ template<> struct FilePathConv { } }; +// std::string_view -> FilePath +// Assumes utf8 encoding +inline FilePath filepathFrom(std::string_view strUtf8) { + return std_filesystem::u8path(strUtf8); +} + } // namespace Mayo + diff --git a/src/base/global.h b/src/base/global.h index 3ca0c212..bbcd4fe2 100644 --- a/src/base/global.h +++ b/src/base/global.h @@ -6,6 +6,26 @@ #pragma once +#if defined(WIN64) || defined(_WIN64) || defined(__WIN64__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) +# define MAYO_OS_WINDOWS +#elif defined(__APPLE__) +# define MAYO_OS_MAC +#elif defined(__linux__) || defined(__linux) +# define MAYO_OS_LINUX +#elif defined(__ANDROID__) || defined(ANDROID) +# define MAYO_OS_ANDROID +# define MAYO_OS_LINUX +#elif defined(__WEBOS__) +# define MAYO_OS_WEBOS +# define MAYO_OS_LINUX +#elif defined(__EMSCRIPTEN__) +# define MAYO_OS_WASM +#endif + +#if !defined(MAYO_OS_WINDOWS) +# define MAYO_OS_UNIX +#endif + // Avoid "unused parameter" warnings #define MAYO_UNUSED(x) (void)x; diff --git a/src/base/io_format.cpp b/src/base/io_format.cpp index c4f21d72..4a93cc29 100644 --- a/src/base/io_format.cpp +++ b/src/base/io_format.cpp @@ -26,6 +26,7 @@ std::string_view formatIdentifier(Format format) case Format_AMF: return "AMF"; case Format_DXF: return "DXF"; case Format_PLY: return "PLY"; + case Format_OFF: return "OFF"; } return ""; @@ -46,6 +47,7 @@ std::string_view formatName(Format format) case Format_AMF: return "Additive manufacturing file format(ISO/ASTM 52915:2016)"; case Format_DXF: return "Drawing Exchange Format"; case Format_PLY: return "Polygon File Format"; + case Format_OFF: return "Object File Format"; } return ""; @@ -64,6 +66,7 @@ Span formatFileSuffixes(Format format) static std::string_view amf_suffix[] = { "amf" }; static std::string_view dxf_suffix[] = { "dxf" }; static std::string_view ply_suffix[] = { "ply" }; + static std::string_view off_suffix[] = { "off" }; switch (format) { case Format_Unknown: return {}; @@ -78,6 +81,7 @@ Span formatFileSuffixes(Format format) case Format_AMF: return amf_suffix; case Format_DXF: return dxf_suffix; case Format_PLY: return ply_suffix; + case Format_OFF: return off_suffix; } return {}; @@ -89,7 +93,8 @@ bool formatProvidesBRep(Format format) return std::any_of( std::cbegin(brepFormats), std::cend(brepFormats), - [=](Format candidate) { return candidate == format; }); + [=](Format candidate) { return candidate == format; } + ); } bool formatProvidesMesh(Format format) diff --git a/src/base/io_format.h b/src/base/io_format.h index 4687cf5e..da7de0a8 100644 --- a/src/base/io_format.h +++ b/src/base/io_format.h @@ -25,7 +25,8 @@ enum Format { Format_VRML, Format_AMF, Format_DXF, - Format_PLY + Format_PLY, + Format_OFF }; // Returns identifier(unique short name) corresponding to 'format' diff --git a/src/base/io_single_format_factory.h b/src/base/io_single_format_factory.h new file mode 100644 index 00000000..c5fc426e --- /dev/null +++ b/src/base/io_single_format_factory.h @@ -0,0 +1,88 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/io_reader.h" +#include "../base/io_writer.h" + +namespace Mayo { +namespace IO { + +template +class SingleFormatFactoryReader : public FactoryReader { +public: + Span formats() const override; + std::unique_ptr create(Format format) const override; + std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; +}; + +template +class SingleFormatFactoryWriter : public FactoryWriter { +public: + Span formats() const override; + std::unique_ptr create(Format format) const override; + std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; +}; + +// -- +// -- Implementation +// -- + +template +Span SingleFormatFactoryReader::formats() const +{ + static const Format arrayFormat[] = { Fmt }; + return arrayFormat; +} + +template +std::unique_ptr SingleFormatFactoryReader::create(Format format) const +{ + if (format == Fmt) + return std::make_unique(); + else + return {}; +} + +template +std::unique_ptr +SingleFormatFactoryReader::createProperties(Format format, PropertyGroup* parentGroup) const +{ + if (format == Fmt) + return FormatReader::createProperties(parentGroup); + else + return {}; +} + +template +Span SingleFormatFactoryWriter::formats() const +{ + static const Format arrayFormat[] = { Fmt }; + return arrayFormat; +} + +template +std::unique_ptr SingleFormatFactoryWriter::create(Format format) const +{ + if (format == Fmt) + return std::make_unique(); + else + return {}; +} + +template +std::unique_ptr +SingleFormatFactoryWriter::createProperties(Format format, PropertyGroup* parentGroup) const +{ + if (format == Fmt) + return FormatWriter::createProperties(parentGroup); + else + return {}; +} + +} // namespace IO +} // namespace Mayo diff --git a/src/base/io_system.cpp b/src/base/io_system.cpp index 86ac965b..da47e5e4 100644 --- a/src/base/io_system.cpp +++ b/src/base/io_system.cpp @@ -57,7 +57,7 @@ Format System::probeFormat(const FilePath& filepath) const FormatProbeInput probeInput = {}; probeInput.filepath = filepath; probeInput.contentsBegin = std::string_view(buff.data(), file.gcount()); - probeInput.hintFullSize = std::filesystem::file_size(filepath); + probeInput.hintFullSize = filepathFileSize(filepath); for (const FormatProbe& fnProbe : m_vecFormatProbe) { const Format format = fnProbe(probeInput); if (format != Format_Unknown) @@ -264,7 +264,7 @@ bool System::importInDocument(const Args_ImportInDocument& args) args.entityPostProcess(labelEntity, &subProgress); } }; - auto fnAddModelTreeEntities = [&](TaskData& taskData) { + auto fnAddModelTreeEntities = [&](const TaskData& taskData) { for (const TDF_Label& labelEntity : taskData.seqTransferredEntity) doc->addEntityTreeNode(labelEntity); }; @@ -285,7 +285,7 @@ bool System::importInDocument(const Args_ImportInDocument& args) vecTaskData.resize(listFilepath.size()); TaskManager childTaskManager; - QObject::connect(&childTaskManager, &TaskManager::progressChanged, [&](TaskId, int) { + childTaskManager.signalProgressChanged.connectSlot([&](TaskId, int) { rootProgress->setValue(childTaskManager.globalProgress()); }); @@ -372,6 +372,13 @@ System::Operation_ExportApplicationItems::targetFormat(Format format) { return *this; } +System::Operation_ExportApplicationItems& +System::Operation_ExportApplicationItems::withItem(const ApplicationItem& appItem) { + m_args.applicationItems = { &appItem, 1 }; + return *this; +} + + System::Operation_ExportApplicationItems& System::Operation_ExportApplicationItems::withItems(Span appItems) { m_args.applicationItems = appItems; @@ -590,6 +597,12 @@ Format probeFormat_PLY(const System::FormatProbeInput& input) return matchRegExp(input.contentsBegin, rx) ? Format_PLY : Format_Unknown; } +Format probeFormat_OFF(const System::FormatProbeInput& input) +{ + const std::regex rx{ R"(^\s*[CN4]?OFF\s+)" }; + return matchRegExp(input.contentsBegin, rx) ? Format_OFF : Format_Unknown; +} + void addPredefinedFormatProbes(System* system) { if (!system) @@ -601,6 +614,7 @@ void addPredefinedFormatProbes(System* system) system->addFormatProbe(probeFormat_STL); system->addFormatProbe(probeFormat_OBJ); system->addFormatProbe(probeFormat_PLY); + system->addFormatProbe(probeFormat_OFF); } } // namespace IO diff --git a/src/base/io_system.h b/src/base/io_system.h index cde76a39..77dcb1b5 100644 --- a/src/base/io_system.h +++ b/src/base/io_system.h @@ -113,7 +113,7 @@ class System { // Format in which items are exported Format targetFormat = Format_Unknown; - // Optional: format-specific parameters to be considered when writting items + // Optional: format-specific parameters to be considered when writing items const PropertyGroup* parameters = nullptr; // TODO use ParametersProvider instead? // Optional: the messenger object used to report any additional infos, warnings and errors @@ -163,6 +163,7 @@ class System { using Operation = Operation_ExportApplicationItems; Operation& targetFile(const FilePath& filepath); Operation& targetFormat(Format format); + Operation& withItem(const ApplicationItem& appItem); Operation& withItems(Span appItems); Operation& withParameters(const PropertyGroup* parameters); Operation& withMessenger(Messenger* messenger); @@ -179,7 +180,7 @@ class System { // Helpers - // Iterate over `spanItem` and call `fnCallback` for each item. Garantees that doublon items + // Iterate over `spanItem` and call `fnCallback` for each item. Guarantees that doublon items // will be visited only once static void visitUniqueItems( Span spanItem, @@ -187,7 +188,7 @@ class System { ); // Iterate over `spanItem` and then deep traverse the corresponding tree node to - // call `fnCallback` for each item. Garantees that doublon items will be visited only once + // call `fnCallback` for each item. Guarantees that doublon items will be visited only once static void traverseUniqueItems( Span spanItem, std::function fnCallback, @@ -210,6 +211,7 @@ Format probeFormat_OCCBREP(const System::FormatProbeInput& input); Format probeFormat_STL(const System::FormatProbeInput& input); Format probeFormat_OBJ(const System::FormatProbeInput& input); Format probeFormat_PLY(const System::FormatProbeInput& input); +Format probeFormat_OFF(const System::FormatProbeInput& input); void addPredefinedFormatProbes(System* system); } // namespace IO diff --git a/src/base/label_data.cpp b/src/base/label_data.cpp new file mode 100644 index 00000000..0a8a9a4f --- /dev/null +++ b/src/base/label_data.cpp @@ -0,0 +1,34 @@ +#include "label_data.h" + +#include "caf_utils.h" +#include "brep_utils.h" +#include "triangulation_annex_data.h" +#include "point_cloud_data.h" +#include "xcaf.h" + +namespace Mayo { + +LabelDataFlags findLabelDataFlags(const TDF_Label& label) +{ + LabelDataFlags flags = LabelData_None; + + if (CafUtils::hasAttribute(label)) + flags |= LabelData_HasTriangulationAnnexData; + + if (XCaf::isShape(label)) { + flags |= LabelData_HasShape; + const TopoDS_Shape shape = XCaf::shape(label); + if (shape.ShapeType() == TopAbs_FACE) { + flags |= LabelData_ShapeIsFace; + if (BRepUtils::isGeometric(TopoDS::Face(shape))) + flags |= LabelData_ShapeIsGeometricFace; + } + } + + if (CafUtils::hasAttribute(label)) + flags |= LabelData_HasPointCloudData; + + return flags; +} + +} // namespace Mayo diff --git a/src/base/label_data.h b/src/base/label_data.h new file mode 100644 index 00000000..6aa31925 --- /dev/null +++ b/src/base/label_data.h @@ -0,0 +1,30 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +class TDF_Label; + +namespace Mayo { + +enum LabelData { + LabelData_None = 0x00000, + LabelData_HasShape = 0x00001, // Label has BRep shape attribute(see XCAFDoc_ShapeTool) + LabelData_ShapeIsFace = 0x00002, // Associated BRep shape is a face(see TopoDS_Face) + LabelData_ShapeIsGeometricFace = 0x00004, // BRep face is geometric(it's associated to a surface) + LabelData_HasTriangulationAnnexData = 0x00080, // Label has TriangulationAnnexData attribute + LabelData_HasPointCloudData = 0x00100, // Label has PointCloudData attribute + LabelData_Custom1 = 0x01000, + LabelData_Custom2 = 0x02000, + LabelData_Custom3 = 0x03000, + LabelData_Custom4 = 0x04000, + LabelData_Custom5 = 0x08000 +}; +using LabelDataFlags = unsigned; + +LabelDataFlags findLabelDataFlags(const TDF_Label& label); + +} // namespace Mayo diff --git a/src/base/libtree.h b/src/base/libtree.h index 7d4bd0bd..c802bffd 100644 --- a/src/base/libtree.h +++ b/src/base/libtree.h @@ -6,6 +6,7 @@ #pragma once +#include "cpp_utils.h" #include "span.h" #include #include @@ -237,8 +238,8 @@ typename Tree::TreeNode* Tree::appendChild(TreeNodeId parentId) template bool Tree::isNodeDeleted(TreeNodeId id) const { - const typename Tree::TreeNode* ptrNode = this->ptrNode(id); - return !ptrNode || ptrNode->isDeleted; + const auto node = this->ptrNode(id); + return !node || node->isDeleted; } template void Tree::removeRoot(TreeNodeId id) @@ -300,7 +301,7 @@ template void traverseTree_unorder(const Tree& tree, const FN& callback) { for (const typename Tree::TreeNode& node : tree.m_vecNode) { - const TreeNodeId id = (&node - &tree.m_vecNode.front()) + 1; + const auto id = CppUtils::safeStaticCast((&node - &tree.m_vecNode.front()) + 1); if (!tree.isNodeDeleted(id)) callback(id); } diff --git a/src/base/math_utils.cpp b/src/base/math_utils.cpp index b71a3d7f..99802260 100644 --- a/src/base/math_utils.cpp +++ b/src/base/math_utils.cpp @@ -16,11 +16,6 @@ namespace Mayo { namespace MathUtils { -double mappedValue(double val, double omin, double omax, double nmin, double nmax) -{ - return (((val - omin) * (nmax - nmin)) / (omax - omin)) + nmin; -} - bool isReversedStandardDir(const gp_Dir& n) { for (int i = 0; i < 3; ++i) { diff --git a/src/base/math_utils.h b/src/base/math_utils.h index bc3a23d3..6eb974d5 100644 --- a/src/base/math_utils.h +++ b/src/base/math_utils.h @@ -20,9 +20,12 @@ struct BndBoxCoords; namespace MathUtils { // Returns the value 'val' which is in range [omin..omax] to the corresponding value in range [nmin..nmax] -double mappedValue(double val, double omin, double omax, double nmin, double nmax); +template +double mappedValue(T val, T1 omin, T2 omax, T3 nmin, T4 nmax); -// TODO: add toPercent() function +// Returns the value 'val' which is in range [omin..omax] to the corresponding percent [0..100] +template +double toPercent(T val, T1 omin, T2 omax); // Is 'n' a standard direction being reversed(ie -X, -Y or -Z) ? bool isReversedStandardDir(const gp_Dir& n); @@ -61,6 +64,21 @@ inline bool fuzzyEqual(double d1, double d2) { // -- Implementation // -- +template +double mappedValue(T val, T1 omin, T2 omax, T3 nmin, T4 nmax) +{ + const auto dist1 = static_cast(omax - omin); + const auto dist2 = nmax - nmin; + const auto distVal = val - omin; + return ((distVal * dist2) / dist1) + nmin; +} + +template +double toPercent(T val, T1 omin, T2 omax) +{ + return mappedValue(val, omin, omax, 0, 100); +} + template T lerp(T a, T b, U t) { #ifdef __cpp_lib_interpolate diff --git a/src/base/mesh_access.cpp b/src/base/mesh_access.cpp index a2f85827..f02255b7 100644 --- a/src/base/mesh_access.cpp +++ b/src/base/mesh_access.cpp @@ -9,7 +9,7 @@ #include "brep_utils.h" #include "caf_utils.h" #include "cpp_utils.h" -#include "data_triangulation.h" +#include "triangulation_annex_data.h" #include "document.h" #include "document_tree_node.h" @@ -31,20 +31,35 @@ class XCafFace_MeshAccess : public IMeshAccess { { const DocumentPtr& doc = treeNode.document(); const TDF_Label labelNode = treeNode.label(); - if (doc->xcaf().isShapeSubOf(labelNode, face)) - m_faceColor = findShapeColor(doc, doc->xcaf().findShapeLabel(face)); + if (doc->xcaf().isShapeSubOf(labelNode, face)) { + const TDF_Label shapeLabel = doc->xcaf().findShapeLabel(face); + if (!shapeLabel.IsNull()) + m_faceColor = findShapeColor(doc, shapeLabel); + } if (!m_faceColor) m_faceColor = findShapeColor(doc, labelNode); + if (!m_faceColor) { + auto annexData = CafUtils::findAttribute(labelNode); + if (annexData) + m_nodeColors = annexData->nodeColors(); + } + const TopLoc_Location locShape = XCaf::shapeAbsoluteLocation(doc->modelTree(), treeNode.id()); TopLoc_Location locFace; m_triangulation = BRep_Tool::Triangulation(face, locFace); m_location = locShape * locFace; } - std::optional nodeColor(int /*i*/) const override { - return m_faceColor; + std::optional nodeColor(int i) const override + { + if (m_faceColor) + return m_faceColor; + else if (!m_nodeColors.empty()) + return m_nodeColors[i]; + else + return {}; } const TopLoc_Location& location() const override { @@ -72,39 +87,11 @@ class XCafFace_MeshAccess : public IMeshAccess { } std::optional m_faceColor; + Span m_nodeColors; TopLoc_Location m_location; Handle(Poly_Triangulation) m_triangulation; }; -// Provides access to a mesh stored within a DataTriangulation attribute -class DataTriangulation_MeshAccess : public IMeshAccess { -public: - DataTriangulation_MeshAccess(const DocumentTreeNode& treeNode) - : m_dataTriangulation(CafUtils::findAttribute(treeNode.label())) - { - } - - std::optional nodeColor(int i) const override - { - if (0 <= i && CppUtils::cmpLess(i, m_dataTriangulation->nodeColors().size())) - return m_dataTriangulation->nodeColors()[i]; - else - return {}; - } - - const TopLoc_Location& location() const override { - return m_location; - } - - const Handle(Poly_Triangulation)& triangulation() const override { - return m_dataTriangulation->Get(); - } - -private: - opencascade::handle m_dataTriangulation; - TopLoc_Location m_location; -}; - void IMeshAccess_visitMeshes( const DocumentTreeNode& treeNode, std::function fnCallback) @@ -121,9 +108,6 @@ void IMeshAccess_visitMeshes( fnProxyCallback(XCafFace_MeshAccess(treeNode, face)); }); } - else if (CafUtils::hasAttribute(treeNode.label())) { - fnProxyCallback(DataTriangulation_MeshAccess(treeNode)); - } } } // namespace Mayo diff --git a/src/base/mesh_utils.cpp b/src/base/mesh_utils.cpp index 827f9a6a..129e370e 100644 --- a/src/base/mesh_utils.cpp +++ b/src/base/mesh_utils.cpp @@ -37,7 +37,7 @@ double MeshUtils::triangulationVolume(const Handle_Poly_Triangulation& triangula double volume = 0; // TODO Parallelize computation - for (const Poly_Triangle& tri : triangulation->Triangles()) { + for (const Poly_Triangle& tri : MeshUtils::triangles(triangulation)) { int v1, v2, v3; tri.Get(v1, v2, v3); volume += MeshUtils::triangleSignedVolume( @@ -56,7 +56,7 @@ double MeshUtils::triangulationArea(const Handle_Poly_Triangulation& triangulati double area = 0; // TODO Parallelize computation - for (const Poly_Triangle& tri : triangulation->Triangles()) { + for (const Poly_Triangle& tri : MeshUtils::triangles(triangulation)) { int v1, v2, v3; tri.Get(v1, v2, v3); area += MeshUtils::triangleArea( diff --git a/src/base/mesh_utils.h b/src/base/mesh_utils.h index 93055722..4175c25c 100644 --- a/src/base/mesh_utils.h +++ b/src/base/mesh_utils.h @@ -30,6 +30,15 @@ struct MeshUtils { static void setNormal(const Handle_Poly_Triangulation& triangulation, int index, const Poly_Triangulation_NormalType& n); static void allocateNormals(const Handle_Poly_Triangulation& triangulation); + static const Poly_Array1OfTriangle& triangles(const Handle_Poly_Triangulation& triangulation) { +#if OCC_VERSION_HEX < 0x070600 + return triangulation->Triangles(); +#else + // Note: Poly_Triangulation::Triangles() was deprecated starting from OpenCascade v7.6.0 + return triangulation->InternalTriangles(); +#endif + } + enum class Orientation { Unknown, Clockwise, diff --git a/src/base/messenger_client.h b/src/base/messenger_client.h index 26d48769..843a5750 100644 --- a/src/base/messenger_client.h +++ b/src/base/messenger_client.h @@ -11,7 +11,7 @@ namespace Mayo { class Messenger; // Provides a link to a messenger object -// The object returned by MessengerClient::messenger() is garanteed to be non-nullptr +// The object returned by MessengerClient::messenger() is guaranteed to be non-nullptr class MessengerClient { public: MessengerClient(); diff --git a/src/base/meta_enum.h b/src/base/meta_enum.h index a2a740e3..05f9090e 100644 --- a/src/base/meta_enum.h +++ b/src/base/meta_enum.h @@ -16,18 +16,18 @@ namespace Mayo { // Currently it wraps magic_enum 3rdparty library class MetaEnum { public: - template - static std::string_view name(ENUM enumValue) { + template + static std::string_view name(EnumType enumValue) { return magic_enum::enum_name(enumValue); } - template + template static int count() { - return magic_enum::enum_count(); + return magic_enum::enum_count(); } - template - static std::string_view nameWithoutPrefix(ENUM enumValue, std::string_view strPrefix) { + template + static std::string_view nameWithoutPrefix(EnumType enumValue, std::string_view strPrefix) { std::string_view strEnumValueName = MetaEnum::name(enumValue); if (strEnumValueName.find(strPrefix) == 0) return strEnumValueName.substr(strPrefix.size()); @@ -36,14 +36,14 @@ class MetaEnum { } // Returns std::array with pairs(value, name), sorted by enum value - template + template static auto entries() { - return magic_enum::enum_entries(); + return magic_enum::enum_entries(); } - template + template static auto values() { - return magic_enum::enum_values(); + return magic_enum::enum_values(); } }; diff --git a/src/base/occ_progress_indicator.cpp b/src/base/occ_progress_indicator.cpp index d5fcce21..2527c050 100644 --- a/src/base/occ_progress_indicator.cpp +++ b/src/base/occ_progress_indicator.cpp @@ -8,6 +8,8 @@ #include "string_conv.h" #include "task_progress.h" +#include + namespace Mayo { OccProgressIndicator::OccProgressIndicator(TaskProgress* progress) @@ -28,7 +30,7 @@ void OccProgressIndicator::Show(const Message_ProgressScope& scope, const bool i } const double pc = this->GetPosition(); // Always within [0,1] - const int val = pc * 100; + const int val = std::clamp(static_cast(pc * 100), 0, 100); if (m_lastProgress != val || isForce) { m_progress->setValue(val); m_lastProgress = val; diff --git a/src/base/point_cloud_data.cpp b/src/base/point_cloud_data.cpp new file mode 100644 index 00000000..05a11dce --- /dev/null +++ b/src/base/point_cloud_data.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "point_cloud_data.h" + +#include +#include + +namespace Mayo { + +const Standard_GUID& PointCloudData::GetID() +{ + static const Standard_GUID PointCloudDataID("ca1b07ac-d6b7-4210-9728-6e4af6a0df50"); + return PointCloudDataID; +} + +PointCloudDataPtr PointCloudData::Set(const TDF_Label& label) +{ + PointCloudDataPtr data; + if (!label.FindAttribute(PointCloudData::GetID(), data)) { + data = new PointCloudData; + label.AddAttribute(data); + } + + return data; +} + +PointCloudDataPtr PointCloudData::Set(const TDF_Label& label, const Handle(Graphic3d_ArrayOfPoints)& points) +{ + PointCloudDataPtr data = PointCloudData::Set(label); + data->m_points = points; + return data; +} + +const Standard_GUID& PointCloudData::ID() const +{ + return PointCloudData::GetID(); +} + +void PointCloudData::Restore(const Handle(TDF_Attribute)& attribute) +{ + auto data = PointCloudDataPtr::DownCast(attribute); + if (data) + m_points = data->m_points; +} + +Handle(TDF_Attribute) PointCloudData::NewEmpty() const +{ + return new PointCloudData; +} + +void PointCloudData::Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)&) const +{ + auto data = PointCloudDataPtr::DownCast(into); + if (data) + data->m_points = m_points; +} + +Standard_OStream& PointCloudData::Dump(Standard_OStream& ostr) const +{ + ostr << "PointCloudData -- "; + TDF_Attribute::Dump(ostr); + return ostr; +} + +} // namespace Mayo diff --git a/src/base/point_cloud_data.h b/src/base/point_cloud_data.h new file mode 100644 index 00000000..6c09f063 --- /dev/null +++ b/src/base/point_cloud_data.h @@ -0,0 +1,41 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include +#include + +namespace Mayo { + +// Pre-declarations +class PointCloudData; +DEFINE_STANDARD_HANDLE(PointCloudData, TDF_Attribute) +using PointCloudDataPtr = Handle(PointCloudData); + +// Provides a label attribute to store point cloud data +class PointCloudData : public TDF_Attribute { +public: + static const Standard_GUID& GetID(); + static PointCloudDataPtr Set(const TDF_Label& label); + static PointCloudDataPtr Set(const TDF_Label& label, const Handle(Graphic3d_ArrayOfPoints)& points); + + const Handle(Graphic3d_ArrayOfPoints)& points() const { return m_points; } + + // -- from TDF_Attribute + const Standard_GUID& ID() const override; + void Restore(const Handle(TDF_Attribute)& attribute) override; + Handle(TDF_Attribute) NewEmpty() const override; + void Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)& table) const override; + Standard_OStream& Dump(Standard_OStream& ostr) const override; + + DEFINE_STANDARD_RTTI_INLINE(PointCloudData, TDF_Attribute) + +private: + Handle(Graphic3d_ArrayOfPoints) m_points; +}; + +} // namespace Mayo diff --git a/src/base/property.cpp b/src/base/property.cpp index 42582fbe..d98acb31 100644 --- a/src/base/property.cpp +++ b/src/base/property.cpp @@ -25,7 +25,7 @@ void PropertyGroup::restoreDefaults() { } -void PropertyGroup::onPropertyAboutToChange(Property *prop) +void PropertyGroup::onPropertyAboutToChange(Property* prop) { if (m_parentGroup) m_parentGroup->onPropertyAboutToChange(prop); @@ -129,7 +129,6 @@ bool Property::hasGroup() const return m_group != nullptr; } - PropertyChangedBlocker::PropertyChangedBlocker(PropertyGroup* group) : m_group(group) { @@ -143,22 +142,16 @@ PropertyChangedBlocker::~PropertyChangedBlocker() m_group->blockPropertyChanged(false); } - -PropertyGroupSignals::PropertyGroupSignals(QObject* parent) - : QObject(parent) -{ -} - void PropertyGroupSignals::onPropertyAboutToChange(Property* prop) { PropertyGroup::onPropertyAboutToChange(prop); - emit propertyAboutToChange(prop); + this->signalPropertyAboutToChange.send(prop); } void PropertyGroupSignals::onPropertyChanged(Property* prop) { PropertyGroup::onPropertyChanged(prop); - emit propertyChanged(prop); + this->signalPropertyChanged.send(prop); } } // namespace Mayo diff --git a/src/base/property.h b/src/base/property.h index 1f146c63..df1a7116 100644 --- a/src/base/property.h +++ b/src/base/property.h @@ -6,11 +6,10 @@ #pragma once +#include "signal.h" #include "span.h" #include "text_id.h" -#include -#include #include #include @@ -74,8 +73,7 @@ struct PropertyChangedBlocker { }; #define Mayo_PropertyChangedBlocker(group) \ - Mayo::PropertyChangedBlocker __Mayo_PropertyChangedBlocker(group); \ - Q_UNUSED(__Mayo_PropertyChangedBlocker); + [[maybe_unused]] Mayo::PropertyChangedBlocker __Mayo_PropertyChangedBlocker(group); class Property { public: @@ -127,21 +125,16 @@ class Property { bool m_isEnabled = true; }; -class PropertyGroupSignals : public QObject, public PropertyGroup { - Q_OBJECT +class PropertyGroupSignals : public PropertyGroup { public: - PropertyGroupSignals(QObject* parent = nullptr); - -signals: - void propertyAboutToChange(Mayo::Property* prop); - void propertyChanged(Mayo::Property* prop); + Signal signalPropertyAboutToChange; + Signal signalPropertyChanged; protected: void onPropertyAboutToChange(Property* prop) override; void onPropertyChanged(Property* prop) override; }; - // -- // -- Implementation // -- @@ -168,6 +161,3 @@ template bool Property::setValueHelper(Property* prop, T* ptrValue, } } // namespace Mayo - -Q_DECLARE_METATYPE(Mayo::Property*) -Q_DECLARE_METATYPE(const Mayo::Property*) diff --git a/src/base/property_builtins.h b/src/base/property_builtins.h index 18a098c1..e7974f88 100644 --- a/src/base/property_builtins.h +++ b/src/base/property_builtins.h @@ -98,14 +98,14 @@ class BasePropertyQuantity : BasePropertyQuantity(PropertyGroup* grp, const TextId& name); }; -template +template class GenericPropertyQuantity : public BasePropertyQuantity { public: - using QuantityType = Quantity; + using QuantityType = Quantity; GenericPropertyQuantity(PropertyGroup* grp, const TextId& name); - Unit quantityUnit() const override { return UNIT; } + Unit quantityUnit() const override { return U; } double quantityValue() const override { return this->quantity().value(); } bool setQuantityValue(double v) override; @@ -186,19 +186,19 @@ GenericScalarProperty::GenericScalarProperty( // GenericPropertyQuantity<> -template -GenericPropertyQuantity::GenericPropertyQuantity(PropertyGroup* grp, const TextId& name) +template +GenericPropertyQuantity::GenericPropertyQuantity(PropertyGroup* grp, const TextId& name) : BasePropertyQuantity(grp, name) { } -template -bool GenericPropertyQuantity::setQuantityValue(double v) +template +bool GenericPropertyQuantity::setQuantityValue(double v) { return this->setQuantity(QuantityType(v)); } -template -bool GenericPropertyQuantity::setQuantity(Quantity qty) +template +bool GenericPropertyQuantity::setQuantity(Quantity qty) { return Property::setValueHelper(this, &m_quantity, qty); } diff --git a/src/base/property_enumeration.h b/src/base/property_enumeration.h index e234a0fa..fad122a7 100644 --- a/src/base/property_enumeration.h +++ b/src/base/property_enumeration.h @@ -24,7 +24,7 @@ namespace Mayo { class PropertyEnumeration : public Property { public: - // This constructor should be used when 'enumeration' is garanteed to be fully constructed + // This constructor should be used when 'enumeration' is guaranteed to be fully constructed PropertyEnumeration(PropertyGroup* grp, const TextId& name, const Enumeration* enumeration); const Enumeration& enumeration() const; @@ -60,12 +60,10 @@ class PropertyEnumeration : public Property { std::vector m_vecDescription; }; -template +template class PropertyEnum : public PropertyEnumeration { - static_assert(std::is_enum::value, "ENUM must be an enumeration type"); + static_assert(std::is_enum::value, "ENUM must be an enumeration type"); public: - using EnumType = ENUM; - PropertyEnum(PropertyGroup* grp, const TextId& name); Enumeration& mutableEnumeration() { return m_enum; } @@ -85,35 +83,35 @@ class PropertyEnum : public PropertyEnumeration { // -- Implementation -template -PropertyEnum::PropertyEnum(PropertyGroup* grp, const TextId& name) +template +PropertyEnum::PropertyEnum(PropertyGroup* grp, const TextId& name) : PropertyEnumeration(grp, name), m_enum(Enumeration::fromType()) { this->setEnumeration(&m_enum); } -template -ENUM PropertyEnum::value() const { +template +EnumType PropertyEnum::value() const { return static_cast(PropertyEnumeration::value()); } -template -bool PropertyEnum::setValue(EnumType value) { +template +bool PropertyEnum::setValue(EnumType value) { return PropertyEnumeration::setValue(static_cast(value)); } -template -void PropertyEnum::addDescription(EnumType value, std::string_view descr) { +template +void PropertyEnum::addDescription(EnumType value, std::string_view descr) { PropertyEnumeration::addDescription(static_cast(value), descr); } -template -void PropertyEnum::setDescriptions(std::initializer_list> initList) +template +void PropertyEnum::setDescriptions(std::initializer_list> initList) { this->clearDescriptions(); - for (const auto& [value, description] : initList) - this->addDescription(value, description); + for (const auto& [val, descr] : initList) + this->addDescription(val, descr); } } // namespace Mayo diff --git a/src/base/property_value_conversion.cpp b/src/base/property_value_conversion.cpp index de223236..694a44d0 100644 --- a/src/base/property_value_conversion.cpp +++ b/src/base/property_value_conversion.cpp @@ -15,7 +15,9 @@ #include "unit_system.h" #include -#include +#if __cpp_lib_to_chars +# include +#endif #include #include #include diff --git a/src/base/quantity.h b/src/base/quantity.h index ca5fcc01..1a9d98cb 100644 --- a/src/base/quantity.h +++ b/src/base/quantity.h @@ -11,6 +11,7 @@ namespace Mayo { +// Numerical value of a unit of measurement template class Quantity { using Qty = Quantity; public: @@ -47,6 +48,11 @@ template class Quantity { double m_value; }; +template constexpr Quantity operator*(Quantity lhs, double rhs); +template constexpr Quantity operator*(double lhs, Quantity rhs); +template constexpr double operator/(Quantity lhs, Quantity rhs); + +// Type aliases for most used quantity types in Mayo using QuantityLength = Quantity; using QuantityArea = Quantity; using QuantityVolume = Quantity; @@ -56,9 +62,6 @@ using QuantityAngle = Quantity; using QuantityVelocity = Quantity; using QuantityDensity = Quantity; -template constexpr Quantity operator*(Quantity lhs, double rhs); -template constexpr Quantity operator*(double lhs, Quantity rhs); - constexpr QuantityArea operator*(QuantityLength lhs, QuantityLength rhs); constexpr QuantityVolume operator*(QuantityLength lhs, QuantityArea rhs); constexpr QuantityVolume operator*(QuantityArea lhs, QuantityLength rhs); @@ -122,6 +125,7 @@ constexpr QuantityMass Quantity_Ounce(0.0283495231); constexpr QuantityMass Quantity_Stone(6.35029318); constexpr QuantityMass Quantity_Hundredweights(50.80234544); +constexpr QuantityTime Quantity_Millisecond(1e-3); constexpr QuantityTime Quantity_Second(1.); constexpr QuantityTime Quantity_Minute(60.); constexpr QuantityTime Quantity_Hour(3600.); @@ -153,6 +157,10 @@ template constexpr Quantity operator*(double lhs, Quantity rhs) { return rhs * lhs; } +template constexpr double operator/(Quantity lhs, Quantity rhs) { + return lhs.value() / rhs.value(); +} + constexpr QuantityArea operator*(QuantityLength lhs, QuantityLength rhs) { return QuantityArea(lhs.value() * rhs.value()); } diff --git a/src/base/settings.cpp b/src/base/settings.cpp index ed8099c3..bfc27840 100644 --- a/src/base/settings.cpp +++ b/src/base/settings.cpp @@ -106,9 +106,8 @@ class Settings::Private { const PropertyValueConversion* m_propValueConverter = nullptr; }; -Settings::Settings(QObject* parent) - : QObject(parent), - d(new Private) +Settings::Settings() + : d(new Private) { } @@ -148,6 +147,12 @@ void Settings::loadProperty(Settings::SettingIndex index) this->loadPropertyFrom(d->storage(), index); } +void Settings::loadProperty(const Property* property) +{ + const auto idProp = this->findProperty(property); + this->loadPropertyFrom(d->storage(), idProp); +} + void Settings::loadPropertyFrom(const Storage& source, SettingIndex index) { Property* prop = this->property(index); @@ -322,9 +327,9 @@ Settings::SettingIndex Settings::findProperty(const Property* property) const for (const Settings_Section& section : group.vecSection) { for (const Settings_Setting& setting : section.vecSetting) { if (setting.property == property) { - const int idSetting = &setting - §ion.vecSetting.front(); - const int idSection = §ion - &group.vecSection.front(); - const int idGroup = &group - &d->m_vecGroup.front(); + const auto idSetting = &setting - §ion.vecSetting.front(); + const auto idSection = §ion - &group.vecSection.front(); + const auto idGroup = &group - &d->m_vecGroup.front(); return SettingIndex(SectionIndex(GroupIndex(idGroup), idSection), idSetting); } } @@ -394,19 +399,19 @@ void Settings::resetSection(SectionIndex index) void Settings::onPropertyAboutToChange(Property* prop) { PropertyGroup::onPropertyAboutToChange(prop); - emit this->aboutToChange(prop); + this->signalAboutToChange.send(prop); } void Settings::onPropertyChanged(Property* prop) { PropertyGroup::onPropertyChanged(prop); - emit this->changed(prop); + this->signalChanged.send(prop); } void Settings::onPropertyEnabled(Property* prop, bool on) { PropertyGroup::onPropertyEnabled(prop, on); - emit this->enabled(prop, on); + this->signalEnabled.send(prop, on); } } // namespace Mayo diff --git a/src/base/settings.h b/src/base/settings.h index 102f905a..cdd2c891 100644 --- a/src/base/settings.h +++ b/src/base/settings.h @@ -9,16 +9,15 @@ #include "property.h" #include "property_value_conversion.h" #include "settings_index.h" +#include "signal.h" -#include #include #include #include namespace Mayo { -class Settings : public QObject, public PropertyGroup { - Q_OBJECT +class Settings : public PropertyGroup { public: using Variant = PropertyValueConversion::Variant; @@ -37,7 +36,7 @@ class Settings : public QObject, public PropertyGroup { using ExcludePropertyPredicate = std::function; using ResetFunction = std::function; - Settings(QObject* parent = nullptr); + Settings(); Settings(const Settings&) = delete; // Not copyable Settings& operator=(const Settings&) = delete; // Not copyable ~Settings(); @@ -46,6 +45,7 @@ class Settings : public QObject, public PropertyGroup { void load(); void loadProperty(SettingIndex index); + void loadProperty(const Property* property); Variant findValueFromKey(std::string_view strKey) const; void save(); @@ -84,10 +84,10 @@ class Settings : public QObject, public PropertyGroup { void resetGroup(GroupIndex index); void resetSection(SectionIndex index); -signals: - void aboutToChange(Mayo::Property* setting); - void changed(Mayo::Property* setting); - void enabled(Mayo::Property* setting, bool on); + // Signals + Signal signalAboutToChange; + Signal signalChanged; + Signal signalEnabled; protected: void onPropertyAboutToChange(Property* prop) override; diff --git a/src/base/signal.cpp b/src/base/signal.cpp new file mode 100644 index 00000000..05e85fd6 --- /dev/null +++ b/src/base/signal.cpp @@ -0,0 +1,23 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "signal.h" + +namespace Mayo { + +std::unique_ptr globalHelper; + +void setGlobalSignalThreadHelper(std::unique_ptr helper) +{ + globalHelper = std::move(helper); +} + +ISignalThreadHelper* getGlobalSignalThreadHelper() +{ + return globalHelper.get(); +} + +} // namespace Mayo diff --git a/src/base/signal.h b/src/base/signal.h new file mode 100644 index 00000000..ecc3b0a3 --- /dev/null +++ b/src/base/signal.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +// 'emit' macro might have been defined by Qt as a pseudo additional keyword +// It's temporarily undefined in this header for the sake of KBindings::Signal which provides member +// function emit() +#ifdef emit +# undef emit +# define MAYO_QT_EMIT_TO_BE_RESTORED +#endif + +#include +#include +#include +#include + +namespace Mayo { + +using SignalConnectionHandle = KDBindings::ConnectionHandle; + +// Provides an interface to deal with signal/slot thread mismatch +// +// Typical example is to connect a signal to a slot in main(UI) thread but later on the signal is +// emitted from within a worker thread. In such case slots would be executed by the worker thread +// which might cause troubles +// ISignalThreadHelper is used by Signal::connectSlot() to make sure the slots are executed in the +// thread where the initial connection was created. +class ISignalThreadHelper { +public: + virtual ~ISignalThreadHelper() = default; + + // Get implementation-specific context object for the current thread + virtual std::any getCurrentThreadContext() = 0; + + // Executes function 'fn' in target thread referred by 'context' + // If current thread is different from target thread then implementation has to ensure 'fn' is + // executed in target thread(eg by enqueuing 'fn' in some event loop) + // 'context' is the implementation-specific thread context object identified in a previous call + // to getCurrentThreadContext() + virtual void execInThread(const std::any& context, const std::function& fn) = 0; +}; + +// Getter/setter functions of the global ISignalThreadHelper object used by Signal::connectSlot() +// ISignalThreadHelper is optional(by default getGlobalSignalThreadHelper() returns nullptr) in that +// case expect signal/slot thread mismatches(which might be fine with user client-code) +ISignalThreadHelper* getGlobalSignalThreadHelper(); +void setGlobalSignalThreadHelper(std::unique_ptr helper); + +// Provides a mechanism for communication between objects +// Based on KDBindings::Signal<> +// +// Prefer connectSlot() in client-code instead of KDBindings::Signal::connect() because it +// uses ISignalThreadHelper to handle signal/slot thread mismatch +// On signal emission KDBindings::Signal makes direct call to the connected slot functions which is +// cause of problems +template +class Signal : public KDBindings::Signal { +public: + Signal() = default; + Signal(const Signal&) = delete; + Signal& operator=(Signal const &other) = delete; + Signal(Signal&& other) noexcept = default; + Signal& operator=(Signal&& other) noexcept = default; + + // Emits the Signal, which causes all connected slots to be called, as long as they are not + // blocked. + // The arguments provided to emit will be passed to each slot by copy, therefore consider + // using (const) references as the Args to the Signal wherever possible. + void send(Args... p) const + { + this->emit(p...); + } + + // Connects 'fnSlot' to the signal + // When send() is called, the functions will be executed with the arguments provided to send() + SignalConnectionHandle connectSlot(const std::function& fnSlot) + { + if (getGlobalSignalThreadHelper()) { + auto threadContext = getGlobalSignalThreadHelper()->getCurrentThreadContext(); + auto connectThreadId = std::this_thread::get_id(); + auto fnWrap = [=](Args... args) { + auto emitThreadId = std::this_thread::get_id(); + if (emitThreadId == connectThreadId) + fnSlot(args...); + else + getGlobalSignalThreadHelper()->execInThread(threadContext, [=]{ fnSlot(args...); }); + }; + return this->connect(fnWrap); + } + else { + return this->connect(fnSlot); + } + } + + // Template overload of Signal::connectSlot() making easier arbitrary connections of functions + // to this signal + // It connects a function to this Signal, binds any provided arguments to that function and + // discards any values emitted by this Signal that aren't needed by the resulting function. + // Especially useful for connecting member functions to signals + template>>, std::integral_constant>>> + SignalConnectionHandle connectSlot(FunctionSlot&& fnSlot, FunctionSlotArgs&&... args) + { + std::function bound = KDBindings::Private::bind_first(std::forward(fnSlot), std::forward(args)...); + return this->connectSlot(bound); + } +}; + +} // namespace Mayo + +// Restore 'emit' Qt macro if it was undefined at the beginning of this header +#ifdef MAYO_QT_EMIT_TO_BE_RESTORED +# define emit +#endif +#undef MAYO_QT_EMIT_TO_BE_RESTORED diff --git a/src/base/string_conv.cpp b/src/base/string_conv.cpp new file mode 100644 index 00000000..eb9db8e9 --- /dev/null +++ b/src/base/string_conv.cpp @@ -0,0 +1,248 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "global.h" +#if defined(MAYO_OS_WINDOWS) +# include +#else +# include +# include +# include +#endif + +#include "string_conv.h" + +#include "cpp_utils.h" +#include "math_utils.h" +#include +#include +#include +#include +#include + +namespace Mayo { + +namespace { + +#if defined(MAYO_OS_WINDOWS) +// See page https://devblogs.microsoft.com/oldnewthing/20161007-00/?p=94475 +// Returns 0 in case of error +UINT getAnsiCodePageForLocale(LCID lcid) +{ + UINT acp = 0; + const LCTYPE infoFlags = LOCALE_IDEFAULTANSICODEPAGE | LOCALE_RETURN_NUMBER; + auto acpAsChars = reinterpret_cast(&acp); + const int sizeInChars = sizeof(acp) / sizeof(TCHAR); + if (GetLocaleInfo(lcid, infoFlags, acpAsChars, sizeInChars) != sizeInChars) + return 0; + + return acp; +} + +LCID getLocaleIdFromName(const char* localeName) +{ + auto fnToUtf16 = [](const char* strUtf8) -> std::wstring { + std::wstring_convert> conv; + return conv.from_bytes(strUtf8); + }; + const std::wstring wlocaleName = fnToUtf16(localeName); + return LocaleNameToLCID(wlocaleName.c_str(), 0/*LOCALE_ALLOW_NEUTRAL_NAMES*/); +} +#endif + +// Convert string 'str' encoded with locale 'strLocale' to UTF8 +std::string toUtf8String(std::string_view str, const std::locale& locale) +{ + if (str.empty()) + return std::string{str}; + +#if defined(MAYO_OS_WINDOWS) + // Retrieve the ANSI code page corresponding to the input locale + const LCID localeId = getLocaleIdFromName(locale.name().c_str()); + const UINT localeAcp = getAnsiCodePageForLocale(localeId); + if (localeAcp == 0) + return std::string{str}; // Assume utf8 + + if (localeAcp == CP_UTF8) + return std::string{str}; // Target locale is already utf8 + + // Compute length of intermediate utf16 string for memory allocation + const int lenStr = CppUtils::safeStaticCast(str.size()); + const int lenUtf16 = MultiByteToWideChar(localeAcp, MB_ERR_INVALID_CHARS, str.data(), lenStr, nullptr, 0); + if (lenUtf16 == 0) + return {}; + + // Encode to intermediate utf16 string + thread_local std::vector utf16; + utf16.resize(lenUtf16 + 1); + const int convCount = MultiByteToWideChar(localeAcp, MB_ERR_INVALID_CHARS, str.data(), lenStr, utf16.data(), lenUtf16); + if (convCount == 0) + return {}; + + utf16.back() = L'\0'; + + // Encode intermediate utf16 string to utf8 + const int lenUtf8 = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), lenUtf16, nullptr, 0, nullptr, nullptr); + thread_local std::vector utf8; + utf8.resize(lenUtf8 + 1); + WideCharToMultiByte(CP_UTF8, 0, utf16.data(), lenUtf16, utf8.data(), lenUtf8, nullptr, nullptr); + utf8.back() = '\0'; + + return utf8.data(); +#else + // Helper function to check 'lhs' and 'rhs' strings are case-insensitive equal + auto fnStringIEqual_c = [](std::string_view lhs, std::string_view rhs) { + if (lhs.size() != rhs.size()) + return false; + + const std::locale& clocale = std::locale::classic(); + for (size_t i = 0; i < lhs.size(); ++i) { + if (std::toupper(lhs.at(i), clocale) != std::toupper(rhs.at(i), clocale)) + return false; + } + + return true; + }; + + // Try to find charset with nl_langinfo_l() POSIX function + const std::string localeName = locale.name(); + std::string charset; + { + const locale_t nullLocale = reinterpret_cast(0); + const locale_t loc = newlocale(LC_CTYPE_MASK, localeName.c_str(), nullLocale); + if (loc != nullLocale) { + const char* zcharset = nl_langinfo_l(CODESET, loc); + charset = zcharset ? zcharset : ""; + freelocale(loc); + } + } + + // Try to find the charset from the locale name(eg UTF-8 in en_US.UTF-8) + if (charset.empty()) { + const auto dotPos = localeName.find('.'); + if (dotPos != std::string::npos) + charset = localeName.substr(dotPos + 1); + } + + // Couldn't find locale charset... + if (charset.empty()) + return std::string{str}; + + // If locale charset is already utf8 encoded then directly return input string + if (fnStringIEqual_c(charset, "UTF-8") || fnStringIEqual_c(charset, "UTF8")) + return std::string{str}; + + // Allocate conversion descriptor + iconv_t convd = iconv_open("UTF-8", charset.c_str()); + [[maybe_unused]] auto _ = gsl::finally([=]{ iconv_close(convd); }); + if (convd == reinterpret_cast(-1)) + return std::string{str}; + + // Attempt to convert input string to utf8(limit attempt count) + thread_local std::vector utf8; + utf8.resize(str.size()); + std::fill(utf8.begin(), utf8.end(), '\0'); + constexpr size_t iconvError = -1; + size_t iconvRes = 0; + constexpr int maxAttemptCount = 10; + int attemptCount = 0; + do { + char* strData = const_cast(str.data()); + size_t lenStr = str.size(); + utf8.resize(utf8.size() + (utf8.size() * 0.25) + 1); // Grow size by 25% each attempt + std::fill(utf8.begin(), utf8.end(), '\0'); + char* utf8Data = utf8.data(); + size_t lenUtf8 = utf8.size(); + iconvRes = iconv(convd, &strData, &lenStr, &utf8Data, &lenUtf8); + ++attemptCount; + } while (iconvRes == iconvError && attemptCount < maxAttemptCount); + + if (iconvRes == iconvError) + return std::string{str}; + + return utf8.data(); +#endif +} + +} // namespace + +std::string to_stdString(double value, const DoubleToStringOptions& opts) +{ + // Helper function to return the last character of string 'str' + auto fnLastChar = [](const std::string& str) { + return !str.empty() ? str.at(str.size() - 1) : char{}; + }; + // Helper function to erase the last character of the string pointed to by 'str' + auto fnEraseLastChar = [](std::string* str) { + if (!str->empty()) + str->erase(str->size() - 1); + }; + + value = opts.roundToZero && MathUtils::fuzzyIsNull(value) ? 0. : value; + std::ostringstream sstr; + sstr.imbue(opts.locale); + sstr << std::setprecision(opts.decimalCount) << std::fixed << value; + std::string str = toUtf8String(sstr.str(), opts.locale); + if (opts.removeTrailingZeroes) { + const char chDecPnt = std::use_facet>(opts.locale).decimal_point(); + if (str.find(chDecPnt) != std::string::npos) { // Remove useless trailing zeroes + while (fnLastChar(str) == '0') + fnEraseLastChar(&str); + + if (fnLastChar(str) == chDecPnt) + fnEraseLastChar(&str); + } + } + + return str; +} + +DoubleToStringOperation::DoubleToStringOperation(double value) + : m_value(value) +{ +} + +DoubleToStringOperation& DoubleToStringOperation::locale(const std::locale& l) +{ + m_opts.locale = l; + return *this; +} + +DoubleToStringOperation& DoubleToStringOperation::decimalCount(int c) +{ + m_opts.decimalCount = c; + return *this; +} + +DoubleToStringOperation& DoubleToStringOperation::removeTrailingZeroes(bool on) +{ + m_opts.removeTrailingZeroes = on; + return *this; +} + +DoubleToStringOperation& DoubleToStringOperation::roundToZero(bool on) +{ + m_opts.roundToZero = on; + return *this; +} + +DoubleToStringOperation::operator std::string() +{ + return to_stdString(m_value, m_opts); +} + +std::string DoubleToStringOperation::get() const +{ + return to_stdString(m_value, m_opts); +} + +DoubleToStringOperation to_stdString(double value) +{ + DoubleToStringOperation op(value); + return op; +} + +} // namespace Mayo diff --git a/src/base/string_conv.h b/src/base/string_conv.h index a335a9c4..97dc9e22 100644 --- a/src/base/string_conv.h +++ b/src/base/string_conv.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -16,44 +17,71 @@ namespace Mayo { // Base converter between string types // Note: unicode(utf8/utf16) conversion is preferred when possible -template struct StringConv {}; +template struct StringConv {}; // -- // -- General API // -- // X -> Y -// Note: 'IN_STRING_TYPE' should be automatically deduced by the compiler -template -OUT_STRING_TYPE string_conv(const IN_STRING_TYPE& str) { - return StringConv::to(str); +// Note: 'InputStringType' should be automatically deduced by the compiler +template +OutputStringType string_conv(const InputStringType& str) { + return StringConv::to(str); } // X -> std::string -template -std::string to_stdString(const STRING_TYPE& str) { +template +std::string to_stdString(const StringType& str) { return string_conv(str); } // X -> std::string_view -template -std::string_view to_stdStringView(const STRING_TYPE& str) { +template +std::string_view to_stdStringView(const StringType& str) { return string_conv(str); } // X -> TCollection_AsciiString -template -TCollection_AsciiString to_OccAsciiString(const STRING_TYPE& str) { +template +TCollection_AsciiString to_OccAsciiString(const StringType& str) { return string_conv(str); } // X -> TCollection_ExtendedString -template -TCollection_ExtendedString to_OccExtString(const STRING_TYPE& str) { +template +TCollection_ExtendedString to_OccExtString(const StringType& str) { return string_conv(str); } +// double -> std::string + +struct DoubleToStringOptions { + std::locale locale; + int decimalCount = 6; + bool removeTrailingZeroes = true; + bool roundToZero = true; + // double zeroPrecision = 0.000000000001; +}; + +class DoubleToStringOperation { +public: + DoubleToStringOperation(double value); + DoubleToStringOperation& locale(const std::locale& l); + DoubleToStringOperation& decimalCount(int c); + DoubleToStringOperation& removeTrailingZeroes(bool on); + DoubleToStringOperation& roundToZero(bool on); + operator std::string(); + std::string get() const; + +private: + double m_value; + DoubleToStringOptions m_opts; +}; + +std::string to_stdString(double value, const DoubleToStringOptions& opts); +DoubleToStringOperation to_stdString(double value); // -- // -- Converters(misc) diff --git a/src/base/task_manager.cpp b/src/base/task_manager.cpp index 51b0fb46..c09b3e9c 100644 --- a/src/base/task_manager.cpp +++ b/src/base/task_manager.cpp @@ -14,11 +14,6 @@ namespace Mayo { -TaskManager::TaskManager(QObject* parent) - : QObject(parent) -{ -} - TaskManager::~TaskManager() { // Make sure all tasks are really finished @@ -91,7 +86,7 @@ void TaskManager::requestAbort(TaskId id) { Entity* entity = this->findEntity(id); if (entity) { - emit this->abortRequested(id); + this->signalAbortRequested.send(id); entity->taskProgress.requestAbort(); } } @@ -111,10 +106,8 @@ int TaskManager::globalProgress() const taskAccumPct += ptrEntity->taskProgress.value(); } - const int taskCount = CppUtils::safeStaticCast(m_mapEntity.size()); - const int newGlobalPct = MathUtils::mappedValue(taskAccumPct, 0, taskCount * 100, 0, 100); + return MathUtils::toPercent(taskAccumPct, 0, m_mapEntity.size() * 100); //qDebug() << "taskCount=" << taskCount << " taskAccumPct=" << taskAccumPct << " newGlobalPct=" << newGlobalPct; - return newGlobalPct; } const std::string& TaskManager::title(TaskId id) const @@ -147,13 +140,13 @@ void TaskManager::execEntity(Entity* entity) if (!entity) return; - emit this->started(entity->task.id()); + this->signalStarted.send(entity->task.id()); const TaskJob& fn = entity->task.job(); fn(&entity->taskProgress); if (!entity->taskProgress.isAbortRequested()) entity->taskProgress.setValue(100); - emit this->ended(entity->task.id()); + this->signalEnded.send(entity->task.id()); entity->isFinished = true; } diff --git a/src/base/task_manager.h b/src/base/task_manager.h index b912da9a..27d30c9b 100644 --- a/src/base/task_manager.h +++ b/src/base/task_manager.h @@ -6,10 +6,10 @@ #pragma once +#include "signal.h" #include "task.h" #include "task_progress.h" -#include #include #include #include @@ -19,10 +19,8 @@ namespace Mayo { -class TaskManager : public QObject { - Q_OBJECT +class TaskManager { public: - TaskManager(QObject* parent = nullptr); ~TaskManager(); TaskId newTask(TaskJob fn); @@ -38,18 +36,18 @@ class TaskManager : public QObject { bool waitForDone(TaskId id, int msecs = -1); void requestAbort(TaskId id); - template - void foreachTask(FUNCTION fn) { + template + void foreachTask(Function fn) { for (const auto& mapPair : m_mapEntity) fn(mapPair.first); } -signals: - void started(Mayo::TaskId id); - void progressStep(Mayo::TaskId id, const std::string& stepTitle); - void progressChanged(Mayo::TaskId id, int percent); - void abortRequested(Mayo::TaskId id); - void ended(Mayo::TaskId id); + // Signals + Signal signalStarted; + Signal signalProgressStep; + Signal signalProgressChanged; + Signal signalAbortRequested; + Signal signalEnded; private: struct Entity { diff --git a/src/base/task_progress.cpp b/src/base/task_progress.cpp index babbe7b3..f2d47c53 100644 --- a/src/base/task_progress.cpp +++ b/src/base/task_progress.cpp @@ -72,7 +72,7 @@ void TaskProgress::setValue(int pct) m_parent->setValue(m_parent->value() + valueDeltaInParent); } else { - emit m_task->manager()->progressChanged(m_task->id(), m_value); + m_task->manager()->signalProgressChanged.send(m_task->id(), m_value); } } @@ -80,7 +80,7 @@ void TaskProgress::setStep(std::string_view title) { if (!this->isNull()) { m_step = title; - emit m_task->manager()->progressStep(m_task->id(), m_step); + m_task->manager()->signalProgressStep.send(m_task->id(), m_step); } } diff --git a/src/base/tkernel_utils.cpp b/src/base/tkernel_utils.cpp index c9a97875..201e63f6 100644 --- a/src/base/tkernel_utils.cpp +++ b/src/base/tkernel_utils.cpp @@ -117,4 +117,13 @@ Quantity_TypeOfColor TKernelUtils::preferredRgbColorType() #endif } +Quantity_Color TKernelUtils::toLinearRgbColor(const Quantity_Color& color) +{ +#if OCC_VERSION_HEX >= 0x070500 + return Quantity_Color{ Quantity_Color::Convert_LinearRGB_To_sRGB(color.Rgb()) }; +#else + return color; +#endif +} + } // namespace Mayo diff --git a/src/base/tkernel_utils.h b/src/base/tkernel_utils.h index 72efb7a9..84720b4d 100644 --- a/src/base/tkernel_utils.h +++ b/src/base/tkernel_utils.h @@ -27,8 +27,8 @@ namespace Mayo { class TKernelUtils { public: - template - static opencascade::handle makeHandle(const STD_TRANSIENT* ptr) { return ptr; } + template + static opencascade::handle makeHandle(const TransientType* ptr) { return ptr; } using ReturnType_StartProgressIndicator = #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) @@ -47,6 +47,9 @@ class TKernelUtils { // Returns the type to be used(by default) for RGB colors, depending on OpenCascasde version static Quantity_TypeOfColor preferredRgbColorType(); + + // Returns a linear-space RGB color from input 'color' expressed with preferredRgbColorType() + static Quantity_Color toLinearRgbColor(const Quantity_Color& color); }; } // namespace Mayo diff --git a/src/base/triangulation_annex_data.cpp b/src/base/triangulation_annex_data.cpp new file mode 100644 index 00000000..5787cb3d --- /dev/null +++ b/src/base/triangulation_annex_data.cpp @@ -0,0 +1,86 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "triangulation_annex_data.h" + +#include +#include +#include +#include + +namespace Mayo { + +const Standard_GUID& TriangulationAnnexData::GetID() +{ + static const Standard_GUID TriangulationAnnexDataID("2a2c7df4-6d97-4c70-b20c-84cb149e26ed"); + return TriangulationAnnexDataID; +} + +TriangulationAnnexDataPtr TriangulationAnnexData::Set(const TDF_Label& label) +{ + TriangulationAnnexDataPtr data; + if (!label.FindAttribute(TriangulationAnnexData::GetID(), data)) { + data = new TriangulationAnnexData; + label.AddAttribute(data); + } + + return data; +} + +TriangulationAnnexDataPtr TriangulationAnnexData::Set( + const TDF_Label& label, Span spanNodeColor) +{ + TriangulationAnnexDataPtr data = TriangulationAnnexData::Set(label); + data->copyNodeColors(spanNodeColor); + return data; +} + +TriangulationAnnexDataPtr TriangulationAnnexData::Set( + const TDF_Label& label, std::vector&& vecNodeColor) +{ + TriangulationAnnexDataPtr data = TriangulationAnnexData::Set(label); + data->m_vecNodeColor = std::move(vecNodeColor); + return data; +} + +const Standard_GUID& TriangulationAnnexData::ID() const +{ + return TriangulationAnnexData::GetID(); +} + +void TriangulationAnnexData::Restore(const Handle(TDF_Attribute)& attribute) +{ + auto data = TriangulationAnnexDataPtr::DownCast(attribute); + if (data) + this->copyNodeColors(data->m_vecNodeColor); +} + +Handle(TDF_Attribute) TriangulationAnnexData::NewEmpty() const +{ + return new TriangulationAnnexData; +} + +void TriangulationAnnexData::Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)&) const +{ + auto data = TriangulationAnnexDataPtr::DownCast(into); + if (data) + data->copyNodeColors(m_vecNodeColor); +} + +Standard_OStream& TriangulationAnnexData::Dump(Standard_OStream& ostr) const +{ + ostr << "TriangulationAnnexData -- "; + TDF_Attribute::Dump(ostr); + return ostr; +} + +void Mayo::TriangulationAnnexData::copyNodeColors(Span spanNodeColor) +{ + m_vecNodeColor.clear(); + std::copy(spanNodeColor.begin(), spanNodeColor.end(), std::back_inserter(m_vecNodeColor)); +} + +} // namespace Mayo diff --git a/src/base/data_triangulation.h b/src/base/triangulation_annex_data.h similarity index 61% rename from src/base/data_triangulation.h rename to src/base/triangulation_annex_data.h index 66422bc2..d8c074af 100644 --- a/src/base/data_triangulation.h +++ b/src/base/triangulation_annex_data.h @@ -9,25 +9,21 @@ #include "span.h" #include -#include +#include #include namespace Mayo { -class DataTriangulation; -DEFINE_STANDARD_HANDLE(DataTriangulation, TDataXtd_Triangulation) -using DataTriangulationPtr = Handle(DataTriangulation); +class TriangulationAnnexData; +DEFINE_STANDARD_HANDLE(TriangulationAnnexData, TDF_Attribute) +using TriangulationAnnexDataPtr = Handle(TriangulationAnnexData); -class DataTriangulation : public TDataXtd_Triangulation { +class TriangulationAnnexData : public TDF_Attribute { public: static const Standard_GUID& GetID(); - static DataTriangulationPtr Set(const TDF_Label& label); - static DataTriangulationPtr Set(const TDF_Label& label, const Handle(Poly_Triangulation)& mesh); - static DataTriangulationPtr Set( - const TDF_Label& label, - const Handle(Poly_Triangulation)& mesh, - Span spanNodeColor - ); + static TriangulationAnnexDataPtr Set(const TDF_Label& label); + static TriangulationAnnexDataPtr Set(const TDF_Label& label, Span spanNodeColor); + static TriangulationAnnexDataPtr Set(const TDF_Label& label, std::vector&& vecNodeColor); Span nodeColors() const { return m_vecNodeColor; } @@ -38,7 +34,7 @@ class DataTriangulation : public TDataXtd_Triangulation { void Paste(const Handle(TDF_Attribute)& into, const Handle(TDF_RelocationTable)& table) const override; Standard_OStream& Dump(Standard_OStream& ostr) const override; - DEFINE_STANDARD_RTTI_INLINE(DataTriangulation, TDataXtd_Triangulation) + DEFINE_STANDARD_RTTI_INLINE(TriangulationAnnexData, TDF_Attribute) private: void copyNodeColors(Span spanNodeColor); diff --git a/src/base/typed_scalar.h b/src/base/typed_scalar.h index 9d97a79d..ecb0017b 100644 --- a/src/base/typed_scalar.h +++ b/src/base/typed_scalar.h @@ -10,24 +10,23 @@ namespace Mayo { -template +template class TypedScalar { public: - using ScalarType = SCALAR; - - static_assert(std::is_scalar::value, "Type T is not scalar"); + using ScalarType = Scalar; + static_assert(std::is_scalar::value, "Type T is not scalar"); TypedScalar() = default; - explicit TypedScalar(SCALAR scalar) : m_scalar(scalar) {} + explicit TypedScalar(Scalar scalar) : m_scalar(scalar) {} - SCALAR get() const { return m_scalar; } + Scalar get() const { return m_scalar; } - bool operator==(const TypedScalar& other) const { + bool operator==(const TypedScalar& other) const { return m_scalar == other.m_scalar; } private: - SCALAR m_scalar; + Scalar m_scalar; }; } // namespace Mayo diff --git a/src/base/unit_system.cpp b/src/base/unit_system.cpp index 3e15f6e0..aba843da 100644 --- a/src/base/unit_system.cpp +++ b/src/base/unit_system.cpp @@ -140,6 +140,14 @@ static UnitSystem::TranslateResult translateImperialUK(double value, Unit unit) } } +template +UnitSystem::TranslateResult translateHelper(QuantityType qty, QuantityType qtyFactor, const char* strUnit) +{ + const double factor = qtyFactor.value(); + return { qty.value() / factor, strUnit, factor }; +} + + #if 0 struct Threshold_UnitInfo { double threshold; @@ -286,7 +294,7 @@ UnitSystem::TranslateResult UnitSystem::parseQuantity(std::string_view strQuanti UnitSystem::TranslateResult UnitSystem::translateLength(QuantityLength length, LengthUnit unit) { auto fnTrResult = [=](QuantityLength qtyFactor, const char* strUnit) { - return TranslateResult{ (length / qtyFactor.value()).value(), strUnit, qtyFactor.value() }; + return Internal::translateHelper(length, qtyFactor, strUnit); }; switch (unit) { // SI @@ -316,7 +324,7 @@ UnitSystem::TranslateResult UnitSystem::translateLength(QuantityLength length, L UnitSystem::TranslateResult UnitSystem::translateArea(QuantityArea area, AreaUnit unit) { auto fnTrResult = [=](QuantityArea qtyFactor, const char* strUnit) { - return TranslateResult{ (area / qtyFactor.value()).value(), strUnit, qtyFactor.value() }; + return Internal::translateHelper(area, qtyFactor, strUnit); }; switch (unit) { // SI @@ -349,16 +357,12 @@ UnitSystem::TranslateResult UnitSystem::radians(QuantityAngle angle) UnitSystem::TranslateResult UnitSystem::degrees(QuantityAngle angle) { - const double factor = Quantity_Degree.value(); - const double rad = angle.value(); - return { rad / factor, "°", factor }; + return Internal::translateHelper(angle, Quantity_Degree, "°"); } UnitSystem::TranslateResult UnitSystem::meters(QuantityLength length) { - const double factor = Quantity_Meter.value(); - const double mm = length.value(); - return { mm / factor, "m", factor }; + return Internal::translateHelper(length, Quantity_Meter, "m"); } UnitSystem::TranslateResult UnitSystem::millimeters(QuantityLength length) @@ -376,6 +380,11 @@ UnitSystem::TranslateResult UnitSystem::millimetersPerSecond(QuantityVelocity sp return { speed.value(), "mm/s", 1. }; } +UnitSystem::TranslateResult UnitSystem::milliseconds(QuantityTime duration) +{ + return Internal::translateHelper(duration, Quantity_Millisecond, "ms"); +} + UnitSystem::TranslateResult UnitSystem::seconds(QuantityTime duration) { return { duration.value(), "s", 1. }; diff --git a/src/base/unit_system.h b/src/base/unit_system.h index b6fe0c19..5ffeffbd 100644 --- a/src/base/unit_system.h +++ b/src/base/unit_system.h @@ -23,9 +23,9 @@ class UnitSystem { constexpr operator bool() const { return this->strUnit != nullptr; } }; - template - static TranslateResult translate(Schema schema, Quantity qty) { - return UnitSystem::translate(schema, qty.value(), UNIT); + template + static TranslateResult translate(Schema schema, Quantity qty) { + return UnitSystem::translate(schema, qty.value(), U); } static TranslateResult translate(Schema schema, double value, Unit unit); static TranslateResult parseQuantity(std::string_view strQuantity, Unit* ptrUnit = nullptr); @@ -40,6 +40,7 @@ class UnitSystem { static TranslateResult millimeters(QuantityLength length); static TranslateResult cubicMillimeters(QuantityVolume volume); static TranslateResult millimetersPerSecond(QuantityVelocity speed); + static TranslateResult milliseconds(QuantityTime duration); static TranslateResult seconds(QuantityTime duration); private: diff --git a/src/base/xcaf.cpp b/src/base/xcaf.cpp index f2ccb8cb..378974b3 100644 --- a/src/base/xcaf.cpp +++ b/src/base/xcaf.cpp @@ -97,6 +97,11 @@ TopoDS_Shape XCaf::shape(const TDF_Label& lbl) return XCAFDoc_ShapeTool::GetShape(lbl); } +void XCaf::setShape(const TDF_Label& label, const TopoDS_Shape& shape) +{ + this->shapeTool()->SetShape(label, shape); +} + //QString XCaf::findLabelName(const TDF_Label& lbl) //{ // QString name = CafUtils::labelAttrStdName(lbl); @@ -226,9 +231,9 @@ TDF_Label XCaf::shapeReferred(const TDF_Label& lbl) TDF_LabelSequence XCaf::layers(const TDF_Label& lbl) const { TDF_LabelSequence seq; - auto layerTool = this->layerTool(); - if (layerTool) - layerTool->GetLayers(lbl, seq); + auto tool = this->layerTool(); + if (tool) + tool->GetLayers(lbl, seq); return seq; } @@ -236,9 +241,9 @@ TDF_LabelSequence XCaf::layers(const TDF_Label& lbl) const TCollection_ExtendedString XCaf::layerName(const TDF_Label &lbl) const { TCollection_ExtendedString name; - auto layerTool = this->layerTool(); - if (layerTool) - layerTool->GetLayer(lbl, name); + auto tool = this->layerTool(); + if (tool) + tool->GetLayer(lbl, name); return name; } diff --git a/src/base/xcaf.h b/src/base/xcaf.h index 8bc3b8a9..72f45fdc 100644 --- a/src/base/xcaf.h +++ b/src/base/xcaf.h @@ -43,11 +43,17 @@ class XCaf { Handle_XCAFDoc_VisMaterialTool visMaterialTool() const; #endif + // -- + // -- XCAFDoc_ShapeTool helpers + // -- + TDF_LabelSequence topLevelFreeShapes() const; static TDF_LabelSequence shapeComponents(const TDF_Label& lbl); static TDF_LabelSequence shapeSubs(const TDF_Label& lbl); static TopoDS_Shape shape(const TDF_Label& lbl); + void setShape(const TDF_Label& label, const TopoDS_Shape& shape); + static bool isShape(const TDF_Label& lbl); static bool isShapeFree(const TDF_Label& lbl); static bool isShapeAssembly(const TDF_Label& lbl); @@ -60,9 +66,6 @@ class XCaf { // Is 'shape' a subshape of the shape stored in 'lbl' ? bool isShapeSubOf(const TDF_Label& lbl, const TopoDS_Shape& shape); - bool hasShapeColor(const TDF_Label& lbl) const; - Quantity_Color shapeColor(const TDF_Label& lbl) const; - // Various flags for findShapeLabel() enum FindShapeLabelFlag { // findShapeLabel() will first try to find the shape among the top-level shapes @@ -84,18 +87,33 @@ class XCaf { static TopLoc_Location shapeReferenceLocation(const TDF_Label& lbl); static TDF_Label shapeReferred(const TDF_Label& lbl); + // Returns labels of the top-level free shapes that were not found in 'seqOther' + TDF_LabelSequence diffTopLevelFreeShapes(const TDF_LabelSequence& seqOther) const; + + // -- + // -- XCAFDoc_ColorTool helpers + // -- + + bool hasShapeColor(const TDF_Label& lbl) const; + Quantity_Color shapeColor(const TDF_Label& lbl) const; + + // -- + // -- XCAFDoc_Material helpers + // -- + static QuantityDensity shapeMaterialDensity(const TDF_Label& lbl); static QuantityDensity shapeMaterialDensity(const Handle_XCAFDoc_Material& material); static Handle_XCAFDoc_Material shapeMaterial(const TDF_Label& lbl); + // -- + // -- XCAFDoc_LayerTool helpers + // -- + TDF_LabelSequence layers(const TDF_Label& lbl) const; TCollection_ExtendedString layerName(const TDF_Label& lbl) const; static ValidationProperties validationProperties(const TDF_Label& lbl); - // Returns labels of the top-level free shapes that were not found in 'seqOther' - TDF_LabelSequence diffTopLevelFreeShapes(const TDF_LabelSequence& seqOther) const; - private: XCaf() = default; diff --git a/src/graphics/graphics_create_driver.cpp b/src/graphics/graphics_create_driver.cpp index ce792511..119465b7 100644 --- a/src/graphics/graphics_create_driver.cpp +++ b/src/graphics/graphics_create_driver.cpp @@ -33,7 +33,7 @@ void setFunctionCreateGraphicsDriver(FunctionCreateGraphicsDriver fn) Handle_Graphic3d_GraphicDriver graphicsCreateDriver() { - auto& fn = getFunctionCreateGraphicsDriver(); + const auto& fn = getFunctionCreateGraphicsDriver(); if (fn) return fn(); diff --git a/src/graphics/graphics_create_virtual_window.cpp b/src/graphics/graphics_create_virtual_window.cpp index cab0de55..7b1551a5 100644 --- a/src/graphics/graphics_create_virtual_window.cpp +++ b/src/graphics/graphics_create_virtual_window.cpp @@ -10,20 +10,20 @@ // #defines constants like "None" which causes name clash with GuiDocument::ViewTrihedronMode::None // -- -#if defined(_WIN32) +#include "../base/global.h" + +#ifdef MAYO_OS_WINDOWS # include #endif -#include "../base/global.h" - #include #include -#if defined(_WIN32) +#if defined(MAYO_OS_WINDOWS) # include # include -#elif defined(__APPLE__) +#elif defined(MAYO_OS_MAC) # include -#elif defined(__ANDROID__) +#elif defined(MAYO_OS_ANDROID) # include #else # include @@ -33,7 +33,7 @@ namespace Mayo { Handle_Aspect_Window graphicsCreateVirtualWindow(const Handle_Graphic3d_GraphicDriver& gfxDriver, int wndWidth, int wndHeight) { -#if defined(_WIN32) +#if defined(MAYO_OS_WINDOWS) MAYO_UNUSED(gfxDriver); // Create a "virtual" WNT window being a pure WNT window redefined to be never shown static Handle_WNT_WClass wClass; @@ -43,10 +43,10 @@ Handle_Aspect_Window graphicsCreateVirtualWindow(const Handle_Graphic3d_GraphicD } auto wnd = new WNT_Window("", wClass, WS_POPUP, 0, 0, wndWidth, wndHeight, Quantity_NOC_BLACK); -#elif defined(__APPLE__) +#elif defined(MAYO_OS_MAC) MAYO_UNUSED(gfxDriver); auto wnd = new Cocoa_Window("", 0, 0, wndWidth, wndHeight); -#elif defined(__ANDROID__) +#elif defined(MAYO_OS_ANDROID) MAYO_UNUSED(gfxDriver); auto wnd = new Aspect_NeutralWindow; wnd->SetSize(wndWidth, wndHeight); diff --git a/src/graphics/graphics_mesh_data_source.cpp b/src/graphics/graphics_mesh_data_source.cpp index ad20e53e..607f7155 100644 --- a/src/graphics/graphics_mesh_data_source.cpp +++ b/src/graphics/graphics_mesh_data_source.cpp @@ -6,6 +6,8 @@ #include "graphics_mesh_data_source.h" +#include "../base/mesh_utils.h" + #include #include #include @@ -29,7 +31,7 @@ GraphicsMeshDataSource::GraphicsMeshDataSource(const Handle_Poly_Triangulation& m_nodeCoords->SetValue(i, 3, xyz.Z()); } - const Poly_Array1OfTriangle& aSeq = m_mesh->Triangles(); + const Poly_Array1OfTriangle& aSeq = MeshUtils::triangles(m_mesh); const int lenTriangles = aSeq.Length(); m_elemNormals = new TColStd_HArray2OfReal(1, lenTriangles, 1, 3); m_elemNodes = new TColStd_HArray2OfInteger(1, lenTriangles, 1, 3); diff --git a/src/graphics/graphics_mesh_object_driver.cpp b/src/graphics/graphics_mesh_object_driver.cpp index 1dd4dc93..6d8b7ef3 100644 --- a/src/graphics/graphics_mesh_object_driver.cpp +++ b/src/graphics/graphics_mesh_object_driver.cpp @@ -6,9 +6,11 @@ #include "graphics_mesh_object_driver.h" +#include "../base/brep_utils.h" #include "../base/caf_utils.h" #include "../base/cpp_utils.h" -#include "../base/data_triangulation.h" +#include "../base/label_data.h" +#include "../base/triangulation_annex_data.h" #include "../base/property_builtins.h" #include "../base/xcaf.h" #include "graphics_mesh_data_source.h" @@ -38,18 +40,9 @@ GraphicsMeshObjectDriver::GraphicsMeshObjectDriver() this->setDefaultDisplayMode(MeshVS_DMF_Shading); } -GraphicsObjectDriver::Support GraphicsMeshObjectDriver::supportStatus(const TDF_Label& label) const +GraphicsMeshObjectDriver::Support GraphicsMeshObjectDriver::supportStatus(const TDF_Label& label) const { - if (CafUtils::hasAttribute(label)) - return Support::Complete; - - if (XCaf::isShape(label)) { - const TopoDS_Shape shape = XCaf::shape(label); - if (shape.ShapeType() == TopAbs_FACE) - return Support::Partial; - } - - return Support::None; + return meshSupportStatus(label); } GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) const @@ -57,12 +50,7 @@ GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) Handle_Poly_Triangulation polyTri; Span spanNodeColor; //const TopLoc_Location* ptrLocationPolyTri = nullptr; - auto attrTriangulation = CafUtils::findAttribute(label); - if (attrTriangulation) { - polyTri = attrTriangulation->Get(); - spanNodeColor = attrTriangulation->nodeColors(); - } - else if (XCaf::isShape(label)) { + if (XCaf::isShape(label)) { const TopoDS_Shape shape = XCaf::shape(label); if (shape.ShapeType() == TopAbs_FACE) { auto tface = Handle_BRep_TFace::DownCast(shape.TShape()); @@ -70,6 +58,10 @@ GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) polyTri = tface->Triangulation(); //ptrLocationPolyTri = &shape.Location(); } + + auto attrMeshData = CafUtils::findAttribute(label); + if (attrMeshData) + spanNodeColor = attrMeshData->nodeColors(); } } @@ -93,7 +85,8 @@ GraphicsObjectPtr GraphicsMeshObjectDriver::createObject(const TDF_Label& label) object->GetDrawer()->SetBoolean(MeshVS_DA_DisplayNodes, defaultValues().showNodes); object->GetDrawer()->SetColor(MeshVS_DA_InteriorColor, defaultValues().color); object->GetDrawer()->SetMaterial( - MeshVS_DA_FrontMaterial, Graphic3d_MaterialAspect(defaultValues().material)); + MeshVS_DA_FrontMaterial, Graphic3d_MaterialAspect(defaultValues().material) + ); object->GetDrawer()->SetColor(MeshVS_DA_EdgeColor, defaultValues().edgeColor); object->GetDrawer()->SetBoolean(MeshVS_DA_ColorReflection, true); object->SetDisplayMode(MeshVS_DMF_Shading); @@ -217,18 +210,35 @@ GraphicsMeshObjectDriver::properties(Span spanObject) c return std::make_unique(spanObject); } +GraphicsMeshObjectDriver::Support GraphicsMeshObjectDriver::meshSupportStatus(const TDF_Label& label) +{ + const LabelDataFlags flags = findLabelDataFlags(label); + if (flags & LabelData_ShapeIsFace) { + if (flags & LabelData_HasTriangulationAnnexData) + return GraphicsMeshObjectDriver::Support::Complete; + else + return GraphicsMeshObjectDriver::Support::Partial; + } + + return GraphicsMeshObjectDriver::Support::None; +} + namespace Internal { -Q_GLOBAL_STATIC(GraphicsMeshObjectDriver::DefaultValues, graphicsMeshDefaultValues) +GraphicsMeshObjectDriver::DefaultValues& graphicsMeshDefaultValues() +{ + static GraphicsMeshObjectDriver::DefaultValues global; + return global; +} } // namespace Internal const GraphicsMeshObjectDriver::DefaultValues& GraphicsMeshObjectDriver::defaultValues() { - return *Internal::graphicsMeshDefaultValues; + return Internal::graphicsMeshDefaultValues(); } void GraphicsMeshObjectDriver::setDefaultValues(const DefaultValues& values) { - *Internal::graphicsMeshDefaultValues = values; + Internal::graphicsMeshDefaultValues() = values; } -} -// namespace Mayo + +} // namespace Mayo diff --git a/src/graphics/graphics_mesh_object_driver.h b/src/graphics/graphics_mesh_object_driver.h index 2f1d7ed6..175b95b3 100644 --- a/src/graphics/graphics_mesh_object_driver.h +++ b/src/graphics/graphics_mesh_object_driver.h @@ -25,6 +25,8 @@ class GraphicsMeshObjectDriver : public GraphicsObjectDriver { Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const override; std::unique_ptr properties(Span spanObject) const override; + static Support meshSupportStatus(const TDF_Label& label); + struct DefaultValues { bool showEdges = false; bool showNodes = false; diff --git a/src/graphics/graphics_object_driver.h b/src/graphics/graphics_object_driver.h index 86a67757..d85dff39 100644 --- a/src/graphics/graphics_object_driver.h +++ b/src/graphics/graphics_object_driver.h @@ -8,6 +8,7 @@ #include "graphics_object_ptr.h" #include "../base/enumeration.h" +#include "../base/label_data.h" #include "../base/property.h" #include "../base/span.h" @@ -25,9 +26,9 @@ using GraphicsObjectDriverPtr = Handle(GraphicsObjectDriver); // Each graphics object "knows" the driver which created it: use function GraphicsObjectDriver::get() class GraphicsObjectDriver : public Standard_Transient { public: - enum Support { None, Partial, Complete }; - virtual Support supportStatus(const TDF_Label& label) const = 0; + enum class Support { None, Partial, Complete }; + virtual Support supportStatus(const TDF_Label& label) const = 0; virtual GraphicsObjectPtr createObject(const TDF_Label& label) const = 0; Enumeration::Value defaultDisplayMode() const { return m_defaultDisplayMode; } diff --git a/src/graphics/graphics_point_cloud_object_driver.cpp b/src/graphics/graphics_point_cloud_object_driver.cpp new file mode 100644 index 00000000..08cbe80b --- /dev/null +++ b/src/graphics/graphics_point_cloud_object_driver.cpp @@ -0,0 +1,68 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "graphics_point_cloud_object_driver.h" + +#include "../base/caf_utils.h" +#include "../base/label_data.h" +#include "../base/point_cloud_data.h" + +#include + +namespace Mayo { + +GraphicsPointCloudObjectDriver::GraphicsPointCloudObjectDriver() +{ +} + +GraphicsPointCloudObjectDriver::Support GraphicsPointCloudObjectDriver::supportStatus(const TDF_Label& label) const +{ + return pointCloudSupportStatus(label); +} + +GraphicsObjectPtr GraphicsPointCloudObjectDriver::createObject(const TDF_Label& label) const +{ + if (findLabelDataFlags(label) & LabelData_HasPointCloudData) { + auto attrPointCloudData = CafUtils::findAttribute(label); + auto object = new AIS_PointCloud; + object->SetPoints(attrPointCloudData->points()); + object->SetOwner(this); + return object; + } + + return {}; +} + +void GraphicsPointCloudObjectDriver::applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value /*mode*/) const +{ + this->throwIf_differentDriver(object); +// this->throwIf_invalidDisplayMode(mode); +// GraphicsUtils::AisObject_contextPtr(object)->SetDisplayMode(object, mode, false); +} + +Enumeration::Value GraphicsPointCloudObjectDriver::currentDisplayMode(const GraphicsObjectPtr& object) const +{ + this->throwIf_differentDriver(object); + return object->DisplayMode(); +} + +std::unique_ptr +GraphicsPointCloudObjectDriver::properties(Span spanObject) const +{ + this->throwIf_differentDriver(spanObject); + return {}; +} + +GraphicsPointCloudObjectDriver::Support GraphicsPointCloudObjectDriver::pointCloudSupportStatus(const TDF_Label& label) +{ + const LabelDataFlags flags = findLabelDataFlags(label); + if (flags & LabelData_HasPointCloudData) + return GraphicsObjectDriver::Support::Complete; + else + return GraphicsObjectDriver::Support::None; +} + +} // namespace Mayo diff --git a/src/graphics/graphics_point_cloud_object_driver.h b/src/graphics/graphics_point_cloud_object_driver.h new file mode 100644 index 00000000..86aafac6 --- /dev/null +++ b/src/graphics/graphics_point_cloud_object_driver.h @@ -0,0 +1,34 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "graphics_object_driver.h" + +namespace Mayo { + +// Pre-declarations +class GraphicsPointCloudObjectDriver; +DEFINE_STANDARD_HANDLE(GraphicsPointCloudObjectDriver, GraphicsObjectDriver) +using GraphicsPointCloudObjectDriverPtr = Handle(GraphicsPointCloudObjectDriver); + +// Provides creation and configuration of graphics objects for point clouds +class GraphicsPointCloudObjectDriver : public GraphicsObjectDriver { +public: + GraphicsPointCloudObjectDriver(); + + Support supportStatus(const TDF_Label& label) const override; + GraphicsObjectPtr createObject(const TDF_Label& label) const override; + void applyDisplayMode(GraphicsObjectPtr object, Enumeration::Value mode) const override; + Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const override; + std::unique_ptr properties(Span spanObject) const override; + + static Support pointCloudSupportStatus(const TDF_Label& label); + + DEFINE_STANDARD_RTTI_INLINE(GraphicsPointCloudObjectDriver, GraphicsObjectDriver) +}; + +} // namespace Mayo diff --git a/src/graphics/graphics_scene.cpp b/src/graphics/graphics_scene.cpp index 5265652e..8160b838 100644 --- a/src/graphics/graphics_scene.cpp +++ b/src/graphics/graphics_scene.cpp @@ -11,7 +11,8 @@ #include #include -#include +#include +#include namespace Mayo { @@ -30,14 +31,29 @@ static Handle_V3d_Viewer createOccViewer() // viewer->SetDefaultVisualization(V3d_ZBUFFER); // viewer->SetDefaultShadingModel(V3d_GOURAUD); viewer->SetDefaultLights(); + +// auto ambientLight = new V3d_AmbientLight; +// ambientLight->SetIntensity(0.7f); +// viewer->AddLight(ambientLight); + +// auto dirLight = new V3d_DirectionalLight; +// dirLight->SetIntensity(0.8f); +// dirLight->SetHeadlight(true); +// dirLight->SetDirection(0, 0, -1); +// dirLight->SetSmoothAngle(3.14159265f * 6.f / 180.f); +// viewer->AddLight(dirLight); + viewer->SetLightOn(); + #if 0 for (const Handle(Graphic3d_CLight)& light : viewer->DefinedLights()) { - if (light->Name() == "amblight") { - light->SetIntensity(0.2f); + if (light->Name() == "amblight" || light->Type() == Graphic3d_TypeOfLightSource_Ambient) { + light->SetIntensity(1.f); } - else if (light->Name() == "headlight") { - light->SetIntensity(0.8f); + else if (light->Name() == "headlight" || light->Type() == Graphic3d_TypeOfLightSource_Directional) { + light->SetIntensity(5.f); + light->SetHeadlight(true); + light->SetDirection(0, 0, -1); } } #endif @@ -72,9 +88,8 @@ class GraphicsScene::Private { SelectionMode m_selectionMode = SelectionMode::Single; }; -GraphicsScene::GraphicsScene(QObject* parent) - : QObject(parent), - d(new Private) +GraphicsScene::GraphicsScene() + : d(new Private) { d->m_v3dViewer = Internal::createOccViewer(); d->m_aisContext = new InteractiveContext(d->m_v3dViewer); @@ -129,8 +144,25 @@ void GraphicsScene::eraseObject(const GraphicsObjectPtr& object) void GraphicsScene::redraw() { - if (!d->m_isRedrawBlocked) - d->m_aisContext->UpdateCurrentViewer(); + if (d->m_isRedrawBlocked) + return; + + //d->m_aisContext->UpdateCurrentViewer(); + for (auto itView = d->m_v3dViewer->DefinedViewIterator(); itView.More(); itView.Next()) + this->signalRedrawRequested.send(itView.Value()); +} + +void GraphicsScene::redraw(const Handle_V3d_View& view) +{ + if (d->m_isRedrawBlocked) + return; + + for (auto itView = d->m_v3dViewer->DefinedViewIterator(); itView.More(); itView.Next()) { + if (itView.Value() == view) { + this->signalRedrawRequested.send(view); + break; + } + } } bool GraphicsScene::isRedrawBlocked() const @@ -237,7 +269,7 @@ void GraphicsScene::clearSelection() d->m_aisContext->ClearDetected(false); d->m_aisContext->ClearSelected(false); if (onEntryOwnerSelected) - emit this->selectionChanged(); + this->signalSelectionChanged.send(); } AIS_InteractiveContext* GraphicsScene::aisContextPtr() const @@ -248,14 +280,15 @@ AIS_InteractiveContext* GraphicsScene::aisContextPtr() const void GraphicsScene::toggleOwnerSelection(const GraphicsOwnerPtr& gfxOwner) { auto gfxObject = GraphicsObjectPtr::DownCast( - gfxOwner ? gfxOwner->Selectable() : Handle_SelectMgr_SelectableObject()); + gfxOwner ? gfxOwner->Selectable() : Handle_SelectMgr_SelectableObject() + ); if (GraphicsUtils::AisObject_isVisible(gfxObject)) d->m_aisContext->AddOrRemoveSelected(gfxOwner, false); } -void GraphicsScene::highlightAt(const QPoint& pos, const Handle_V3d_View& view) +void GraphicsScene::highlightAt(int xPos, int yPos, const Handle_V3d_View& view) { - d->m_aisContext->MoveTo(pos.x(), pos.y(), view, false); + d->m_aisContext->MoveTo(xPos, yPos, view, false); } void GraphicsScene::select() @@ -278,7 +311,7 @@ void GraphicsScene::select() #endif } - emit this->selectionChanged(); + this->signalSelectionChanged.send(); } int GraphicsScene::selectedCount() const @@ -304,7 +337,7 @@ void GraphicsScene::setSelectionMode(GraphicsScene::SelectionMode mode) { if (mode != d->m_selectionMode) { d->m_selectionMode = mode; - emit this->selectionModeChanged(); + this->signalSelectionModeChanged.send(); } } diff --git a/src/graphics/graphics_scene.h b/src/graphics/graphics_scene.h index b448a500..1245e629 100644 --- a/src/graphics/graphics_scene.h +++ b/src/graphics/graphics_scene.h @@ -6,26 +6,28 @@ #pragma once +#include "../base/signal.h" #include "graphics_object_ptr.h" #include "graphics_owner_ptr.h" #include #include #include -#include #include -class QPoint; namespace Mayo { // Provides a container for GraphicsObject items(actually AIS_InteractiveObject) // It's a wrapper(incomplete though) around AIS_InteractiveContext to provide a more consistent API -class GraphicsScene : public QObject { - Q_OBJECT +class GraphicsScene { public: - GraphicsScene(QObject* parent = nullptr); + GraphicsScene(); ~GraphicsScene(); + // Not copyable + GraphicsScene(const GraphicsScene&) = delete; + GraphicsScene& operator=(const GraphicsScene&) = delete; + opencascade::handle createV3dView(); const opencascade::handle& v3dViewer() const; @@ -39,6 +41,7 @@ class GraphicsScene : public QObject { void eraseObject(const GraphicsObjectPtr& object); void redraw(); + void redraw(const Handle_V3d_View& view); bool isRedrawBlocked() const; void blockRedraw(bool on); @@ -70,7 +73,7 @@ class GraphicsScene : public QObject { void setSelectionMode(SelectionMode mode); const GraphicsOwnerPtr& currentHighlightedOwner() const; - void highlightAt(const QPoint& pos, const Handle_V3d_View& view); + void highlightAt(int xPos, int yPos, const Handle_V3d_View& view); void select(); int selectedCount() const; @@ -79,30 +82,31 @@ class GraphicsScene : public QObject { void toggleOwnerSelection(const GraphicsOwnerPtr& owner); void clearSelection(); - template - void foreachDisplayedObject(FUNCTION fn) const; + template + void foreachDisplayedObject(Function fn) const; - template - void foreachActiveSelectionMode(const GraphicsObjectPtr& object, FUNCTION fn) const; + template + void foreachActiveSelectionMode(const GraphicsObjectPtr& object, Function fn) const; - template - void foreachOwner(const GraphicsObjectPtr& object, int selectionMode, FUNCTION fn) const; + template + void foreachOwner(const GraphicsObjectPtr& object, int selectionMode, Function fn) const; - template - void foreachSelectedOwner(FUNCTION fn) const; + template + void foreachSelectedOwner(Function fn) const; - template - GraphicsOwnerPtr findSelectedOwner(PREDICATE fn) const; + template + GraphicsOwnerPtr findSelectedOwner(Predicate fn) const; -signals: - void selectionChanged(); - void selectionModeChanged(); + // Signals + Signal<> signalSelectionChanged; + Signal<> signalSelectionModeChanged; + Signal signalRedrawRequested; private: AIS_InteractiveContext* aisContextPtr() const; class Private; - Private* const d; + Private* const d = nullptr; }; class GraphicsSceneRedrawBlocker { @@ -127,8 +131,8 @@ class GraphicsSceneRedrawBlocker { // -- Implementation // -- -template -void GraphicsScene::foreachDisplayedObject(FUNCTION fn) const +template +void GraphicsScene::foreachDisplayedObject(Function fn) const { AIS_ListOfInteractive listObject; this->aisContextPtr()->DisplayedObjects(listObject); @@ -136,8 +140,8 @@ void GraphicsScene::foreachDisplayedObject(FUNCTION fn) const fn(ptr); } -template -void GraphicsScene::foreachActiveSelectionMode(const GraphicsObjectPtr& object, FUNCTION fn) const +template +void GraphicsScene::foreachActiveSelectionMode(const GraphicsObjectPtr& object, Function fn) const { TColStd_ListOfInteger listMode; this->aisContextPtr()->ActivatedModes(object, listMode); @@ -145,8 +149,8 @@ void GraphicsScene::foreachActiveSelectionMode(const GraphicsObjectPtr& object, fn(mode); } -template -void GraphicsScene::foreachOwner(const GraphicsObjectPtr& object, int selectionMode, FUNCTION fn) const +template +void GraphicsScene::foreachOwner(const GraphicsObjectPtr& object, int selectionMode, Function fn) const { opencascade::handle mapEntityOwner; this->aisContextPtr()->EntityOwners(mapEntityOwner, object, selectionMode); @@ -154,8 +158,8 @@ void GraphicsScene::foreachOwner(const GraphicsObjectPtr& object, int selectionM fn(*it); } -template -void GraphicsScene::foreachSelectedOwner(FUNCTION fn) const +template +void GraphicsScene::foreachSelectedOwner(Function fn) const { auto context = this->aisContextPtr(); for (context->InitSelected(); context->MoreSelected(); context->NextSelected()) { @@ -163,8 +167,8 @@ void GraphicsScene::foreachSelectedOwner(FUNCTION fn) const } } -template -GraphicsOwnerPtr GraphicsScene::findSelectedOwner(PREDICATE fn) const +template +GraphicsOwnerPtr GraphicsScene::findSelectedOwner(Predicate fn) const { auto context = this->aisContextPtr(); for (context->InitSelected(); context->MoreSelected(); context->NextSelected()) { diff --git a/src/graphics/graphics_shape_object_driver.cpp b/src/graphics/graphics_shape_object_driver.cpp index 37c798fe..484e7bbd 100644 --- a/src/graphics/graphics_shape_object_driver.cpp +++ b/src/graphics/graphics_shape_object_driver.cpp @@ -6,8 +6,10 @@ #include "graphics_shape_object_driver.h" +#include "../base/brep_utils.h" #include "../base/caf_utils.h" -#include "../base/data_triangulation.h" +#include "../base/triangulation_annex_data.h" +#include "../base/label_data.h" #include "../base/xcaf.h" #include "graphics_utils.h" @@ -34,17 +36,7 @@ GraphicsShapeObjectDriver::GraphicsShapeObjectDriver() GraphicsObjectDriver::Support GraphicsShapeObjectDriver::supportStatus(const TDF_Label& label) const { - if (XCaf::isShape(label)) - return Support::Complete; - - // TODO TNaming_Shape ? - // TDataXtd_Shape ? - - if (CafUtils::hasAttribute(label)) { - //return Support::Partial; - } - - return Support::None; + return shapeSupportStatus(label); } GraphicsObjectPtr GraphicsShapeObjectDriver::createObject(const TDF_Label& label) const @@ -55,14 +47,13 @@ GraphicsObjectPtr GraphicsShapeObjectDriver::createObject(const TDF_Label& label object->SetMaterial(Graphic3d_NOM_PLASTER); object->Attributes()->SetFaceBoundaryDraw(true); object->Attributes()->SetFaceBoundaryAspect( - new Prs3d_LineAspect(Quantity_NOC_BLACK, Aspect_TOL_SOLID, 1.)); + new Prs3d_LineAspect(Quantity_NOC_BLACK, Aspect_TOL_SOLID, 1.) + ); object->Attributes()->SetIsoOnTriangulation(true); //object->Attributes()->SetShadingModel(Graphic3d_TypeOfShadingModel_Pbr, true/*overrideDefaults*/); object->SetOwner(this); return object; } - else if (CafUtils::hasAttribute(label)) { - } return {}; } @@ -106,12 +97,10 @@ void GraphicsShapeObjectDriver::applyDisplayMode(GraphicsObjectPtr object, Enume aisLink->ConnectedTo()->Redisplay(true); } else { - object->Redisplay(true); + object->Redisplay(true/*AllModes*/); } } } - - // context->UpdateCurrentViewer(); } Enumeration::Value GraphicsShapeObjectDriver::currentDisplayMode(const GraphicsObjectPtr& object) const @@ -141,4 +130,21 @@ GraphicsShapeObjectDriver::properties(Span spanObject) return {}; } +GraphicsObjectDriver::Support GraphicsShapeObjectDriver::shapeSupportStatus(const TDF_Label& label) +{ + const LabelDataFlags flags = findLabelDataFlags(label); + if (flags & LabelData_ShapeIsFace) { + if (flags & LabelData_ShapeIsGeometricFace) + return GraphicsObjectDriver::Support::Complete; + else if (flags & LabelData_HasTriangulationAnnexData) + return GraphicsObjectDriver::Support::Partial; + } + + if (flags & LabelData_HasShape) + return GraphicsObjectDriver::Support::Complete; + + return GraphicsObjectDriver::Support::None; + // TODO TNaming_Shape? TDataXtd_Shape? +} + } // namespace Mayo diff --git a/src/graphics/graphics_shape_object_driver.h b/src/graphics/graphics_shape_object_driver.h index ef4ad2c2..6e56acb5 100644 --- a/src/graphics/graphics_shape_object_driver.h +++ b/src/graphics/graphics_shape_object_driver.h @@ -25,6 +25,8 @@ class GraphicsShapeObjectDriver : public GraphicsObjectDriver { Enumeration::Value currentDisplayMode(const GraphicsObjectPtr& object) const override; std::unique_ptr properties(Span spanObject) const override; + static Support shapeSupportStatus(const TDF_Label& label); + enum DisplayMode { DisplayMode_Wireframe, DisplayMode_HiddenLineRemoval, diff --git a/src/graphics/graphics_view_ptr.h b/src/graphics/graphics_view_ptr.h new file mode 100644 index 00000000..7f15fe2d --- /dev/null +++ b/src/graphics/graphics_view_ptr.h @@ -0,0 +1,42 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "graphics_scene.h" + +#include + +namespace Mayo { + +// Helper class providing a V3d view created associated with the owner GraphicsScene object +// The redraw() member function actually calls GraphicsScene::redraw() which under the hood sends +// a "redraw requested" signal +class GraphicsViewPtr { +public: + GraphicsViewPtr(GraphicsScene* scene, const Handle_V3d_View& view) + : m_scene(scene), + m_view(view) + {} + + const Handle_V3d_View& v3dView() const { + return m_view; + } + + GraphicsScene* scene() const { + return m_scene; + } + + void redraw() { + m_scene->redraw(m_view); + } + +private: + GraphicsScene* m_scene = nullptr; + Handle_V3d_View m_view; +}; + +} // namespace Mayo diff --git a/src/graphics/v3d_view_camera_animation.h b/src/graphics/v3d_view_camera_animation.h deleted file mode 100644 index 2d985d0a..00000000 --- a/src/graphics/v3d_view_camera_animation.h +++ /dev/null @@ -1,47 +0,0 @@ -/**************************************************************************** -** Copyright (c) 2021, Fougue Ltd. -** All rights reserved. -** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt -****************************************************************************/ - -#pragma once - -#include -#include -#include -#include - -namespace Mayo { - -class V3dViewCameraAnimation : public QAbstractAnimation { -public: - using ViewFunction = std::function; - - V3dViewCameraAnimation(const Handle_V3d_View& view, QObject* parent = nullptr); - - int duration() const override; - void setDuration(int msecs); - - void setCameraStart(const Handle_Graphic3d_Camera& camera); - void setCameraEnd(const Handle_Graphic3d_Camera& camera); - - const QEasingCurve& easingCurve() const; - void setEasingCurve(const QEasingCurve& easingCurve); - - void configure(const ViewFunction& fnViewChange); - - void setRenderFunction(ViewFunction fnViewRender); - -protected: - void updateCurrentTime(int currentTime) override; - -private: - Handle_V3d_View m_view; - Handle_Graphic3d_Camera m_cameraStart; - Handle_Graphic3d_Camera m_cameraEnd; - QEasingCurve m_easingCurve; // Linear by default - int m_duration_ms = 1000; - std::function m_fnViewRender; -}; - -} // namespace Mayo diff --git a/src/gui/gui_application.cpp b/src/gui/gui_application.cpp index 467d1eea..014df79c 100644 --- a/src/gui/gui_application.cpp +++ b/src/gui/gui_application.cpp @@ -15,29 +15,75 @@ namespace Mayo { +struct GuiApplication::Private { + + void onApplicationItemSelectionChanged( + Span selected, Span deselected) + { + std::unordered_set setGuiDocDirty; + auto fnToggleItemSelected = [&](const ApplicationItem& item) { + GuiDocument* guiDoc = m_backPtr->findGuiDocument(item.document()); + if (guiDoc) { + guiDoc->toggleItemSelected(item); + setGuiDocDirty.insert(guiDoc); + } + }; + for (const ApplicationItem& item : selected) + fnToggleItemSelected(item); + + for (const ApplicationItem& item : deselected) + fnToggleItemSelected(item); + + for (GuiDocument* guiDoc : setGuiDocDirty) + guiDoc->graphicsScene()->redraw(); + } + + GuiApplication* m_backPtr = nullptr; + ApplicationPtr m_app; + std::vector m_vecGuiDocument; + std::vector m_vecGfxObjectDriver; + SignalConnectionHandle m_connApplicationItemSelectionChanged; + ApplicationItemSelectionModel m_selectionModel; + bool m_automaticDocumentMapping = true; +}; + GuiApplication::GuiApplication(const ApplicationPtr& app) - : QObject(app.get()), - m_app(app), - m_selectionModel(new ApplicationItemSelectionModel(this)) -{ - QObject::connect( - app.get(), &Application::documentAdded, - this, &GuiApplication::onDocumentAdded); - QObject::connect( - app.get(), &Application::documentAboutToClose, - this, &GuiApplication::onDocumentAboutToClose); + : d(new Private) +{ + d->m_backPtr = this; + d->m_app = app; + + app->signalDocumentAdded.connectSlot(&GuiApplication::onDocumentAdded, this); + app->signalDocumentAboutToClose.connectSlot(&GuiApplication::onDocumentAboutToClose, this); this->connectApplicationItemSelectionChanged(true); } GuiApplication::~GuiApplication() { - for (GuiDocument* guiDoc : m_vecGuiDocument) + for (GuiDocument* guiDoc : d->m_vecGuiDocument) delete guiDoc; + + delete d; +} + +const ApplicationPtr& GuiApplication::application() const +{ + return d->m_app; +} + +Span GuiApplication::guiDocuments() +{ + return d->m_vecGuiDocument; +} + +Span GuiApplication::guiDocuments() const +{ + return d->m_vecGuiDocument; } GuiDocument* GuiApplication::findGuiDocument(const DocumentPtr& doc) const { - for (GuiDocument* guiDoc : m_vecGuiDocument) { + for (GuiDocument* guiDoc : d->m_vecGuiDocument) { if (guiDoc->document() == doc) return guiDoc; } @@ -47,23 +93,28 @@ GuiDocument* GuiApplication::findGuiDocument(const DocumentPtr& doc) const ApplicationItemSelectionModel* GuiApplication::selectionModel() const { - return m_selectionModel; + return &d->m_selectionModel; } void GuiApplication::addGraphicsObjectDriver(GraphicsObjectDriverPtr ptr) { - m_vecGfxObjectDriver.push_back(ptr); + d->m_vecGfxObjectDriver.push_back(ptr); } void GuiApplication::addGraphicsObjectDriver(std::unique_ptr ptr) { - m_vecGfxObjectDriver.push_back(ptr.release()); // Will be converted to opencascade::handle<> + d->m_vecGfxObjectDriver.push_back(ptr.release()); // Will be converted to opencascade::handle<> +} + +Span GuiApplication::graphicsObjectDrivers() const +{ + return d->m_vecGfxObjectDriver; } GraphicsObjectPtr GuiApplication::createGraphicsObject(const TDF_Label& label) const { GraphicsObjectDriver* driverPartialSupport = nullptr; - for (const GraphicsObjectDriverPtr& driver : m_vecGfxObjectDriver) { + for (const GraphicsObjectDriverPtr& driver : d->m_vecGfxObjectDriver) { const GraphicsObjectDriver::Support support = driver->supportStatus(label); if (support == GraphicsObjectDriver::Support::Complete) return driver->createObject(label); @@ -78,60 +129,45 @@ GraphicsObjectPtr GuiApplication::createGraphicsObject(const TDF_Label& label) c return {}; } +bool GuiApplication::automaticDocumentMapping() const +{ + return d->m_automaticDocumentMapping; +} + +void GuiApplication::setAutomaticDocumentMapping(bool on) +{ + d->m_automaticDocumentMapping = on; +} + void GuiApplication::onDocumentAdded(const DocumentPtr& doc) { - if (m_automaticDocumentMapping) { - m_vecGuiDocument.push_back(new GuiDocument(doc, this)); - emit guiDocumentAdded(m_vecGuiDocument.back()); + if (d->m_automaticDocumentMapping) { + d->m_vecGuiDocument.push_back(new GuiDocument(doc, this)); + this->signalGuiDocumentAdded.send(d->m_vecGuiDocument.back()); } } void GuiApplication::onDocumentAboutToClose(const DocumentPtr& doc) { auto itFound = std::find_if( - m_vecGuiDocument.begin(), - m_vecGuiDocument.end(), + d->m_vecGuiDocument.begin(), + d->m_vecGuiDocument.end(), [=](const GuiDocument* guiDoc) { return guiDoc->document() == doc; }); - if (itFound != m_vecGuiDocument.end()) { + if (itFound != d->m_vecGuiDocument.end()) { GuiDocument* guiDoc = *itFound; - m_vecGuiDocument.erase(itFound); - emit guiDocumentErased(guiDoc); + d->m_vecGuiDocument.erase(itFound); + this->signalGuiDocumentErased.send(guiDoc); delete guiDoc; } } void GuiApplication::connectApplicationItemSelectionChanged(bool on) { + d->m_connApplicationItemSelectionChanged.disconnect(); if (on) { - m_connApplicationItemSelectionChanged = QObject::connect( - m_selectionModel, &ApplicationItemSelectionModel::changed, - this, &GuiApplication::onApplicationItemSelectionChanged, - Qt::UniqueConnection); - } - else { - QObject::disconnect(m_connApplicationItemSelectionChanged); + auto& sigChanged = d->m_selectionModel.signalChanged; + d->m_connApplicationItemSelectionChanged = sigChanged.connectSlot(&Private::onApplicationItemSelectionChanged, d); } } -void GuiApplication::onApplicationItemSelectionChanged( - Span selected, Span deselected) -{ - std::unordered_set setGuiDocDirty; - auto fnToggleItemSelected = [&](const ApplicationItem& item) { - GuiDocument* guiDoc = this->findGuiDocument(item.document()); - if (guiDoc) { - guiDoc->toggleItemSelected(item); - setGuiDocDirty.insert(guiDoc); - } - }; - for (const ApplicationItem& item : selected) - fnToggleItemSelected(item); - - for (const ApplicationItem& item : deselected) - fnToggleItemSelected(item); - - for (GuiDocument* guiDoc : setGuiDocDirty) - guiDoc->graphicsScene()->redraw(); -} - } // namespace Mayo diff --git a/src/gui/gui_application.h b/src/gui/gui_application.h index 3dd39c7b..6478f334 100644 --- a/src/gui/gui_application.h +++ b/src/gui/gui_application.h @@ -12,39 +12,41 @@ #include "../graphics/graphics_object_driver.h" #include "gui_document.h" -#include #include namespace Mayo { class GuiDocument; -class GuiApplication : public QObject { - Q_OBJECT +class GuiApplication { public: GuiApplication(const ApplicationPtr& app); ~GuiApplication(); - const ApplicationPtr& application() const { return m_app; } + // Not copyable + GuiApplication(const GuiApplication&) = delete; + GuiApplication& operator=(const GuiApplication&) = delete; - Span guiDocuments() { return m_vecGuiDocument; } - Span guiDocuments() const { return m_vecGuiDocument; } + const ApplicationPtr& application() const; + + Span guiDocuments(); + Span guiDocuments() const; GuiDocument* findGuiDocument(const DocumentPtr& doc) const; ApplicationItemSelectionModel* selectionModel() const; void addGraphicsObjectDriver(GraphicsObjectDriverPtr ptr); void addGraphicsObjectDriver(std::unique_ptr ptr); - Span graphicsObjectDrivers() const { return m_vecGfxObjectDriver; } + Span graphicsObjectDrivers() const; GraphicsObjectPtr createGraphicsObject(const TDF_Label& label) const; // Whether a GuiDocument object is automatically created once a Document is added in Application - bool automaticDocumentMapping() const { return m_automaticDocumentMapping; } - void setAutomaticDocumentMapping(bool on) { m_automaticDocumentMapping = on; } + bool automaticDocumentMapping() const; + void setAutomaticDocumentMapping(bool on); -signals: - void guiDocumentAdded(Mayo::GuiDocument* guiDoc); - void guiDocumentErased(Mayo::GuiDocument* guiDoc); + // Signals + mutable Signal signalGuiDocumentAdded; + mutable Signal signalGuiDocumentErased; protected: void onDocumentAdded(const DocumentPtr& doc); @@ -53,16 +55,9 @@ class GuiApplication : public QObject { private: friend class GuiDocument; void connectApplicationItemSelectionChanged(bool on); - void onApplicationItemSelectionCleared(); - void onApplicationItemSelectionChanged( - Span selected, Span deselected); - - ApplicationPtr m_app; - std::vector m_vecGuiDocument; - ApplicationItemSelectionModel* m_selectionModel = nullptr; - std::vector m_vecGfxObjectDriver; - QMetaObject::Connection m_connApplicationItemSelectionChanged; - bool m_automaticDocumentMapping = true; + + struct Private; + Private* const d = nullptr; }; } // namespace Mayo diff --git a/src/gui/gui_document.cpp b/src/gui/gui_document.cpp index 4c779766..d655a116 100644 --- a/src/gui/gui_document.cpp +++ b/src/gui/gui_document.cpp @@ -6,14 +6,16 @@ #include "gui_document.h" +#include "../base/application.h" #include "../base/application_item.h" #include "../base/bnd_utils.h" #include "../base/caf_utils.h" #include "../base/cpp_utils.h" #include "../base/document.h" +#include "../base/math_utils.h" #include "../base/tkernel_utils.h" -#include "../gui/gui_application.h" #include "../graphics/graphics_utils.h" +#include "../gui/gui_application.h" #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) # include @@ -24,6 +26,8 @@ #include #include +#include + namespace Mayo { namespace Internal { @@ -46,7 +50,8 @@ static Handle_AIS_Trihedron createOriginTrihedron() //aisTrihedron->SetTextColor(Quantity_NOC_GRAY40); aisTrihedron->SetSize(60); aisTrihedron->SetTransformPersistence( - new Graphic3d_TransformPers(Graphic3d_TMF_ZoomPers, axis->Ax2().Location())); + new Graphic3d_TransformPers(Graphic3d_TMF_ZoomPers, axis->Ax2().Location()) + ); aisTrihedron->Attributes()->SetZLayer(Graphic3d_ZLayerId_Topmost); aisTrihedron->SetInfiniteState(true); return aisTrihedron; @@ -63,22 +68,20 @@ static GuiDocument::GradientBackground& defaultGradientBackground() } // namespace Internal GuiDocument::GuiDocument(const DocumentPtr& doc, GuiApplication* guiApp) - : QObject(guiApp), - m_guiApp(guiApp), + : m_guiApp(guiApp), m_document(doc), - m_gfxScene(this), m_v3dView(m_gfxScene.createV3dView()), m_aisOriginTrihedron(Internal::createOriginTrihedron()), - m_cameraAnimation(new V3dViewCameraAnimation(m_v3dView, this)) + m_cameraAnimation(new V3dViewCameraAnimation) { Expects(!doc.IsNull()); #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) this->setViewTrihedronMode(ViewTrihedronMode::AisViewCube); - this->setViewTrihedronCorner(Qt::TopLeftCorner); + this->setViewTrihedronCorner(Aspect_TOTP_LEFT_UPPER); #else this->setViewTrihedronMode(ViewTrihedronMode::V3dViewZBuffer); - this->setViewTrihedronCorner(Qt::BottomLeftCorner); + this->setViewTrihedronCorner(Aspect_TOTP_LEFT_LOWER); #endif //m_v3dView->SetShadingModel(Graphic3d_TypeOfShadingModel_Pbr); @@ -87,7 +90,8 @@ GuiDocument::GuiDocument(const DocumentPtr& doc, GuiApplication* guiApp) m_v3dView->ChangeRenderingParams().NbMsaaSamples = 4; m_v3dView->ChangeRenderingParams().CollectedStats = Graphic3d_RenderingParams::PerfCounters_Extended; m_v3dView->ChangeRenderingParams().StatsPosition = new Graphic3d_TransformPers( - Graphic3d_TMF_2d, Aspect_TOTP_RIGHT_UPPER, Graphic3d_Vec2i(20, 20)); + Graphic3d_TMF_2d, Aspect_TOTP_RIGHT_UPPER, Graphic3d_Vec2i(20, 20) + ); // 3D view - Set gradient background m_v3dView->SetBgGradientColors( GuiDocument::defaultGradientBackground().color1, @@ -96,18 +100,56 @@ GuiDocument::GuiDocument(const DocumentPtr& doc, GuiApplication* guiApp) ); //m_v3dView->SetShadingModel(Graphic3d_TOSM_PBR); - m_cameraAnimation->setEasingCurve(QEasingCurve::OutExpo); + m_cameraAnimation->setView(m_v3dView); for (int i = 0; i < doc->entityCount(); ++i) this->mapEntity(doc->entityTreeNodeId(i)); - QObject::connect(doc.get(), &Document::entityAdded, this, &GuiDocument::onDocumentEntityAdded); - QObject::connect( - doc.get(), &Document::entityAboutToBeDestroyed, - this, &GuiDocument::onDocumentEntityAboutToBeDestroyed); - QObject::connect( - &m_gfxScene, &GraphicsScene::selectionChanged, - this, &GuiDocument::onGraphicsSelectionChanged); + doc->signalEntityAdded.connectSlot(&GuiDocument::onDocumentEntityAdded, this); + doc->signalEntityAboutToBeDestroyed.connectSlot(&GuiDocument::onDocumentEntityAboutToBeDestroyed, this); + m_gfxScene.signalSelectionChanged.connectSlot(&GuiDocument::onGraphicsSelectionChanged, this); +} + +void GuiDocument::setDevicePixelRatio(double ratio) +{ + if (MathUtils::fuzzyEqual(m_devicePixelRatio, ratio)) + return; + + m_devicePixelRatio = ratio; + switch (m_viewTrihedronMode) { + case ViewTrihedronMode::None: { + break; + } + case ViewTrihedronMode::V3dViewZBuffer: { + this->v3dViewTrihedronDisplay(m_viewTrihedronCorner); + break; + } + case ViewTrihedronMode::AisViewCube: { +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) + auto viewCube = Handle(AIS_ViewCube)::DownCast(m_aisViewCube); + if (viewCube) { + viewCube->SetSize(55 * m_devicePixelRatio, true/*adaptOtherParams*/); + viewCube->SetFontHeight(12 * m_devicePixelRatio); + const int xyOffset = int(std::round(85 * m_devicePixelRatio)); + viewCube->SetTransformPersistence( + new Graphic3d_TransformPers( + Graphic3d_TMF_TriedronPers, + m_viewTrihedronCorner, + Graphic3d_Vec2i(xyOffset, xyOffset) + ) + ); + viewCube->Redisplay(true/*allModes*/); + } +#endif + + break; + } + } // endswitch +} + +GuiDocument::~GuiDocument() +{ + delete m_cameraAnimation; } void GuiDocument::foreachGraphicsObject( @@ -128,13 +170,13 @@ void GuiDocument::foreachGraphicsObject( }); } -TreeNodeId GuiDocument::nodeFromGraphicsObject(const GraphicsObjectPtr& object) const +TreeNodeId GuiDocument::nodeFromGraphicsObject(const GraphicsObjectPtr& gfxObject) const { - if (!object) + if (!gfxObject) return 0; for (const GraphicsEntity& gfxEntity: m_vecGraphicsEntity) { - auto it = gfxEntity.mapGfxObjectTreeNode.find(object); + auto it = gfxEntity.mapGfxObjectTreeNode.find(gfxObject); if (it != gfxEntity.mapGfxObjectTreeNode.cend()) return it->second; } @@ -227,7 +269,7 @@ void GuiDocument::setNodeVisible(TreeNodeId nodeId, bool on) traverseTree(nodeId, docModelTree , [=](TreeNodeId id) { fnSetNodeVisibleState(id, nodeVisibleState); }); - this->foreachGraphicsObject(nodeId, [=](GraphicsObjectPtr gfxObject){ + this->foreachGraphicsObject(nodeId, [=](GraphicsObjectPtr gfxObject) { GraphicsUtils::AisObject_setVisible(gfxObject, on); }); @@ -282,7 +324,7 @@ void GuiDocument::setNodeVisible(TreeNodeId nodeId, bool on) // Notify all node visibility changes if (!mapNodeIdVisibleState.empty()) - emit nodesVisibilityChanged(mapNodeIdVisibleState); + this->signalNodesVisibilityChanged.send(mapNodeIdVisibleState); } void GuiDocument::setExplodingFactor(double t) @@ -312,13 +354,13 @@ void GuiDocument::toggleOriginTrihedronVisibility() m_gfxScene.setObjectVisible(m_aisOriginTrihedron, visible); } -bool GuiDocument::processAction(const GraphicsOwnerPtr& graphicsOwner) +bool GuiDocument::processAction(const GraphicsOwnerPtr& gfxOwner) { - if (graphicsOwner.IsNull()) + if (!gfxOwner) return false; #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) - auto viewCubeOwner = opencascade::handle::DownCast(graphicsOwner); + auto viewCubeOwner = Handle(AIS_ViewCubeOwner)::DownCast(gfxOwner); if (viewCubeOwner) { this->setViewCameraOrientation(viewCubeOwner->MainOrientation()); return true; @@ -338,8 +380,8 @@ void GuiDocument::setViewCameraOrientation(V3d_TypeOfOrientation projection) void GuiDocument::runViewCameraAnimation(const V3dViewCameraAnimation::ViewFunction& fnViewChange) { - m_cameraAnimation->configure(fnViewChange); - m_cameraAnimation->start(QAbstractAnimation::KeepWhenStopped); + m_cameraAnimation->configureCameraChange(fnViewChange); + m_cameraAnimation->start(); } void GuiDocument::stopViewCameraAnimation() @@ -347,18 +389,6 @@ void GuiDocument::stopViewCameraAnimation() m_cameraAnimation->stop(); } -static Aspect_TypeOfTriedronPosition toOccCorner(Qt::Corner corner) -{ - switch (corner) { - case Qt::TopLeftCorner: return Aspect_TOTP_LEFT_UPPER; - case Qt::TopRightCorner: return Aspect_TOTP_RIGHT_UPPER; - case Qt::BottomLeftCorner: return Aspect_TOTP_LEFT_LOWER; - case Qt::BottomRightCorner: return Aspect_TOTP_RIGHT_LOWER; - } - - return Aspect_TOTP_LEFT_UPPER; // Fallback -} - void GuiDocument::setViewTrihedronMode(ViewTrihedronMode mode) { if (mode == m_viewTrihedronMode) @@ -391,8 +421,9 @@ void GuiDocument::setViewTrihedronMode(ViewTrihedronMode mode) aisViewCube->SetTransformPersistence( new Graphic3d_TransformPers( Graphic3d_TMF_TriedronPers, - toOccCorner(m_viewTrihedronCorner), - Graphic3d_Vec2i(85, 85))); + m_viewTrihedronCorner, + Graphic3d_Vec2i(85, 85)) + ); m_gfxScene.addObject(aisViewCube); //aisViewCube->Attributes()->DatumAspect()->LineAspect(Prs3d_DP_XAxis)->SetColor(Quantity_NOC_RED2); const Handle_Prs3d_DatumAspect& datumAspect = aisViewCube->Attributes()->DatumAspect(); @@ -410,10 +441,10 @@ void GuiDocument::setViewTrihedronMode(ViewTrihedronMode mode) } // endswitch m_viewTrihedronMode = mode; - emit this->viewTrihedronModeChanged(mode); + this->signalViewTrihedronModeChanged.send(mode); } -void GuiDocument::setViewTrihedronCorner(Qt::Corner corner) +void GuiDocument::setViewTrihedronCorner(Aspect_TypeOfTriedronPosition corner) { if (corner == m_viewTrihedronCorner) return; @@ -428,14 +459,14 @@ void GuiDocument::setViewTrihedronCorner(Qt::Corner corner) } case ViewTrihedronMode::AisViewCube: { if (m_aisViewCube) - m_aisViewCube->TransformPersistence()->SetCorner2d(toOccCorner(corner)); + m_aisViewCube->TransformPersistence()->SetCorner2d(corner); break; } } // endswitch m_viewTrihedronCorner = corner; - emit this->viewTrihedronCornerChanged(corner); + this->signalViewTrihedronCornerChanged.send(corner); } int GuiDocument::aisViewCubeBoundingSize() const @@ -481,7 +512,7 @@ void GuiDocument::onDocumentEntityAdded(TreeNodeId entityTreeNodeId) { this->mapEntity(entityTreeNodeId); BndUtils::add(&m_gfxBoundingBox, m_vecGraphicsEntity.back().bndBox); - emit graphicsBoundingBoxChanged(m_gfxBoundingBox); + this->signalGraphicsBoundingBoxChanged.send(m_gfxBoundingBox); } void GuiDocument::onDocumentEntityAboutToBeDestroyed(TreeNodeId entityTreeNodeId) @@ -492,7 +523,7 @@ void GuiDocument::onDocumentEntityAboutToBeDestroyed(TreeNodeId entityTreeNodeId for (const GraphicsEntity& gfxEntity : m_vecGraphicsEntity) BndUtils::add(&m_gfxBoundingBox, gfxEntity.bndBox); - emit graphicsBoundingBoxChanged(m_gfxBoundingBox); + this->signalGraphicsBoundingBoxChanged.send(m_gfxBoundingBox); } void GuiDocument::onGraphicsSelectionChanged() @@ -509,7 +540,8 @@ void GuiDocument::onGraphicsSelectionChanged() std::vector vecSelected; m_gfxScene.foreachSelectedOwner([&](const GraphicsOwnerPtr& gfxOwner) { auto gfxObject = GraphicsObjectPtr::DownCast( - gfxOwner ? gfxOwner->Selectable() : Handle_SelectMgr_SelectableObject()); + gfxOwner ? gfxOwner->Selectable() : Handle_SelectMgr_SelectableObject() + ); const TreeNodeId nodeId = this->nodeFromGraphicsObject(gfxObject); if (nodeId != 0) { const ApplicationItem appItem({ m_document, nodeId }); @@ -554,13 +586,23 @@ void GuiDocument::mapEntity(TreeNodeId entityTreeNodeId) } if (!docModelTree.nodeIsRoot(id)) { - Handle_AIS_ConnectedInteractive gfxInstance = new AIS_ConnectedInteractive; - gfxInstance->Connect(gfxProduct, XCaf::shapeAbsoluteLocation(docModelTree, id)); - gfxInstance->SetDisplayMode(gfxProduct->DisplayMode()); - gfxInstance->Attributes()->SetFaceBoundaryDraw(gfxProduct->Attributes()->FaceBoundaryDraw()); - gfxInstance->SetOwner(gfxProduct->GetOwner()); - gfxEntity.vecObject.push_back(GraphicsObjectPtr(gfxInstance)); - if (XCaf::isShapeReference(docModelTree.nodeData(docModelTree.nodeParent(id)))) + const TDF_Label parentNodeLabel = docModelTree.nodeData(docModelTree.nodeParent(id)); + if (XCaf::isShapeReference(parentNodeLabel) && m_document->xcaf().hasShapeColor(parentNodeLabel)) { + // Parent node is a reference and it redefines color attribute, so the graphics + // can't be shared with the product + auto gfxObject = m_guiApp->createGraphicsObject(parentNodeLabel); + gfxEntity.vecObject.push_back(gfxObject); + } + else { + auto gfxInstance = new AIS_ConnectedInteractive; + gfxInstance->Connect(gfxProduct, XCaf::shapeAbsoluteLocation(docModelTree, id)); + gfxInstance->SetDisplayMode(gfxProduct->DisplayMode()); + gfxInstance->Attributes()->SetFaceBoundaryDraw(gfxProduct->Attributes()->FaceBoundaryDraw()); + gfxInstance->SetOwner(gfxProduct->GetOwner()); + gfxEntity.vecObject.push_back(GraphicsObjectPtr(gfxInstance)); + } + + if (XCaf::isShapeReference(parentNodeLabel)) id = docModelTree.nodeParent(id); } else { @@ -606,7 +648,7 @@ void GuiDocument::unmapEntity(TreeNodeId entityTreeNodeId) for (const GraphicsEntity::Object& object : ptrItem->vecObject) m_gfxScene.eraseObject(object.ptr); - const int indexItem = ptrItem - &m_vecGraphicsEntity.front(); + const auto indexItem = ptrItem - &m_vecGraphicsEntity.front(); m_vecGraphicsEntity.erase(m_vecGraphicsEntity.begin() + indexItem); m_gfxScene.redraw(); } @@ -621,14 +663,15 @@ const GuiDocument::GraphicsEntity* GuiDocument::findGraphicsEntity(TreeNodeId en auto itFound = std::find_if( m_vecGraphicsEntity.cbegin(), m_vecGraphicsEntity.cend(), - [=](const GraphicsEntity& item) { return item.treeNodeId == entityTreeNodeId; }); + [=](const GraphicsEntity& item) { return item.treeNodeId == entityTreeNodeId; } + ); return itFound != m_vecGraphicsEntity.cend() ? &(*itFound) : nullptr; } -void GuiDocument::v3dViewTrihedronDisplay(Qt::Corner corner) +void GuiDocument::v3dViewTrihedronDisplay(Aspect_TypeOfTriedronPosition corner) { - constexpr double scale = 0.075; - m_v3dView->TriedronDisplay(toOccCorner(corner), Quantity_NOC_GRAY50, scale, V3d_ZBUFFER); + const double scale = 0.075 * m_devicePixelRatio; + m_v3dView->TriedronDisplay(corner, Quantity_NOC_GRAY50, scale, V3d_ZBUFFER); } } // namespace Mayo diff --git a/src/gui/gui_document.h b/src/gui/gui_document.h index 000a87af..17b3f60a 100644 --- a/src/gui/gui_document.h +++ b/src/gui/gui_document.h @@ -8,12 +8,14 @@ #include "../base/document.h" #include "../base/global.h" +#include "../base/signal.h" #include "../base/tkernel_utils.h" #include "../graphics/graphics_object_driver.h" #include "../graphics/graphics_scene.h" -#include "../graphics/v3d_view_camera_animation.h" +#include "../graphics/graphics_view_ptr.h" +#include "v3d_view_camera_animation.h" -#include +#include #include #include #include @@ -25,12 +27,17 @@ namespace Mayo { class ApplicationItem; class GuiApplication; +class V3dViewCameraAnimation; // Provides the link between Base::Document and graphical representations -class GuiDocument : public QObject { - Q_OBJECT +class GuiDocument { public: GuiDocument(const DocumentPtr& doc, GuiApplication* guiApp); + ~GuiDocument(); + + // Not copyable + GuiDocument(const GuiDocument&) = delete; + GuiDocument& operator=(const GuiDocument&) = delete; const DocumentPtr& document() const { return m_document; } @@ -38,20 +45,28 @@ class GuiDocument : public QObject { const Handle_V3d_View& v3dView() const { return m_v3dView; } GraphicsScene* graphicsScene() { return &m_gfxScene; } + GraphicsViewPtr graphicsView() { return GraphicsViewPtr{ &m_gfxScene, m_v3dView }; } const Bnd_Box& graphicsBoundingBox() const { return m_gfxBoundingBox; } + // Gets/sets the ratio between physical pixels and device-independent pixels for the target window. + // This value is dependent on the screen the window is on, and may have to be updated when the + // target window is moved. + // Common values are 1.0(default) on normal displays and 2.0 on Apple "retina" displays + double devicePixelRatio() const { return m_devicePixelRatio; } + void setDevicePixelRatio(double ratio); + // Executes callback 'fn' on all graphics objects associated to tree node 'nodeId' // This also includes all children(deep node traversal) void foreachGraphicsObject(TreeNodeId nodeId, const std::function& fn) const; // Finds the tree node id associated to graphics object - TreeNodeId nodeFromGraphicsObject(const GraphicsObjectPtr& object) const; + TreeNodeId nodeFromGraphicsObject(const GraphicsObjectPtr& gfxObject) const; // Toggles selected status of an application item(doesn't affect Application's selection model) void toggleItemSelected(const ApplicationItem& appItem); - // Executes action associated to a 3D sensistive item - bool processAction(const GraphicsOwnerPtr& graphicsOwner); + // Executes action associated to a 3D sensitive item + bool processAction(const GraphicsOwnerPtr& gfxOwner); // -- Display mode int activeDisplayMode(const GraphicsObjectDriverPtr& driver) const; @@ -84,8 +99,8 @@ class GuiDocument : public QObject { ViewTrihedronMode viewTrihedronMode() const { return m_viewTrihedronMode; } void setViewTrihedronMode(ViewTrihedronMode mode); - Qt::Corner viewTrihedronCorner() const { return m_viewTrihedronCorner; } - void setViewTrihedronCorner(Qt::Corner corner); + Aspect_TypeOfTriedronPosition viewTrihedronCorner() const { return m_viewTrihedronCorner; } + void setViewTrihedronCorner(Aspect_TypeOfTriedronPosition corner); int aisViewCubeBoundingSize() const; static bool isAisViewCubeObject(const GraphicsObjectPtr& gfxObject); @@ -99,13 +114,12 @@ class GuiDocument : public QObject { static const GradientBackground& defaultGradientBackground(); static void setDefaultGradientBackground(const GradientBackground& gradientBkgnd); -signals: - void nodesVisibilityChanged(const std::unordered_map& mapNodeId); - - void graphicsBoundingBoxChanged(const Bnd_Box& bndBox); - - void viewTrihedronModeChanged(ViewTrihedronMode mode); - void viewTrihedronCornerChanged(Qt::Corner corner); + // Signals + using MapVisibilityByTreeNodeId = std::unordered_map; + mutable Signal signalNodesVisibilityChanged; + mutable Signal signalGraphicsBoundingBoxChanged; + mutable Signal signalViewTrihedronModeChanged; + mutable Signal signalViewTrihedronCornerChanged; // -- Implementation private: @@ -133,17 +147,18 @@ class GuiDocument : public QObject { const GraphicsEntity* findGraphicsEntity(TreeNodeId entityTreeNodeId) const; - void v3dViewTrihedronDisplay(Qt::Corner corner); + void v3dViewTrihedronDisplay(Aspect_TypeOfTriedronPosition corner); GuiApplication* m_guiApp = nullptr; DocumentPtr m_document; GraphicsScene m_gfxScene; Handle_V3d_View m_v3dView; Handle_AIS_InteractiveObject m_aisOriginTrihedron; + double m_devicePixelRatio = 1.; - V3dViewCameraAnimation* m_cameraAnimation; + V3dViewCameraAnimation* m_cameraAnimation = nullptr; ViewTrihedronMode m_viewTrihedronMode = ViewTrihedronMode::None; - Qt::Corner m_viewTrihedronCorner = Qt::BottomLeftCorner; + Aspect_TypeOfTriedronPosition m_viewTrihedronCorner = Aspect_TOTP_LEFT_UPPER; Handle_AIS_InteractiveObject m_aisViewCube; std::vector m_vecGraphicsEntity; diff --git a/src/gui/gui_vkey_mouse.h b/src/gui/gui_vkey_mouse.h new file mode 100644 index 00000000..6b94660a --- /dev/null +++ b/src/gui/gui_vkey_mouse.h @@ -0,0 +1,31 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include + +#if OCC_VERSION_HEX >= 0x070400 +# include +#else +// Excerpted from OpenCascade/inc/Aspect_VKeyFlags.hxx +// This header was introduced with OpenCascade v7.4.0 + +//! Mouse buttons, for combining with Aspect_VKey and Aspect_VKeyFlags. +typedef unsigned int Aspect_VKeyMouse; + +//! Mouse button bitmask +enum +{ + Aspect_VKeyMouse_NONE = 0, //!< no buttons + + Aspect_VKeyMouse_LeftButton = 1 << 13, //!< mouse left button + Aspect_VKeyMouse_MiddleButton = 1 << 14, //!< mouse middle button (scroll) + Aspect_VKeyMouse_RightButton = 1 << 15, //!< mouse right button + + Aspect_VKeyMouse_MainButtons = Aspect_VKeyMouse_LeftButton | Aspect_VKeyMouse_MiddleButton | Aspect_VKeyMouse_RightButton +}; +#endif + +constexpr unsigned int Aspect_VKeyMouse_UNKNOWN = 1 << 24; diff --git a/src/graphics/v3d_view_camera_animation.cpp b/src/gui/v3d_view_camera_animation.cpp similarity index 55% rename from src/graphics/v3d_view_camera_animation.cpp rename to src/gui/v3d_view_camera_animation.cpp index ed135945..45c373d5 100644 --- a/src/graphics/v3d_view_camera_animation.cpp +++ b/src/gui/v3d_view_camera_animation.cpp @@ -6,49 +6,73 @@ #include "v3d_view_camera_animation.h" +#include "../base/unit_system.h" + namespace Mayo { -V3dViewCameraAnimation::V3dViewCameraAnimation(const Handle_V3d_View& view, QObject* parent) - : QAbstractAnimation(parent), - m_view(view), - m_cameraStart(new Graphic3d_Camera), +V3dViewCameraAnimation::V3dViewCameraAnimation() + : m_cameraStart(new Graphic3d_Camera), m_cameraEnd(new Graphic3d_Camera) { } -int V3dViewCameraAnimation::duration() const +void V3dViewCameraAnimation::setBackend(std::unique_ptr anim) { - return m_duration_ms; + m_backend = std::move(anim); + if (m_backend) { + m_backend->setDuration(m_duration); + m_backend->setTimerCallback([=](QuantityTime t){ this->updateCurrentTime(t); }); + } } -void V3dViewCameraAnimation::setDuration(int msecs) +void V3dViewCameraAnimation::setView(const Handle_V3d_View& view) { - m_duration_ms = msecs; + if (this->isRunning()) + this->stop(); + + m_view = view; } -void V3dViewCameraAnimation::setCameraStart(const Handle_Graphic3d_Camera& camera) +void V3dViewCameraAnimation::setDuration(QuantityTime t) { - m_cameraStart->Copy(camera); + m_duration = t; + if (m_backend) + m_backend->setDuration(t); } -void V3dViewCameraAnimation::setCameraEnd(const Handle_Graphic3d_Camera& camera) +bool V3dViewCameraAnimation::isRunning() const { - m_cameraEnd->Copy(camera); + return m_backend ? m_backend->isRunning() : false; } -const QEasingCurve& V3dViewCameraAnimation::easingCurve() const +void V3dViewCameraAnimation::start() { - return m_easingCurve; + if (m_backend) + m_backend->start(); } -void V3dViewCameraAnimation::setEasingCurve(const QEasingCurve& easingCurve) +void V3dViewCameraAnimation::stop() { - m_easingCurve = easingCurve; + if (m_backend) + m_backend->stop(); } -void V3dViewCameraAnimation::configure(const ViewFunction& fnViewChange) +void V3dViewCameraAnimation::setCameraStart(const Handle_Graphic3d_Camera& camera) { - if (this->state() == QAbstractAnimation::Running) + m_cameraStart->Copy(camera); +} + +void V3dViewCameraAnimation::setCameraEnd(const Handle_Graphic3d_Camera& camera) +{ + m_cameraEnd->Copy(camera); +} + +void V3dViewCameraAnimation::configureCameraChange(const ViewFunction& fnViewChange) +{ + if (!m_view) + return; + + if (this->isRunning()) this->stop(); const bool wasImmediateUpdateOn = m_view->SetImmediateUpdate(false); @@ -64,9 +88,12 @@ void V3dViewCameraAnimation::setRenderFunction(ViewFunction fnViewRender) m_fnViewRender = std::move(fnViewRender); } -void V3dViewCameraAnimation::updateCurrentTime(int currentTime) +void V3dViewCameraAnimation::updateCurrentTime(QuantityTime currTime) { - const double t = m_easingCurve.valueForProgress(currentTime / double(m_duration_ms)); + if (!m_view) + return; + + const double t = m_backend ? m_backend->valueForProgress(currTime / m_duration) : 0.; const bool prevImmediateUpdate = m_view->SetImmediateUpdate(false); const Graphic3d_CameraLerp cameraLerp(m_cameraStart, m_cameraEnd); Handle_Graphic3d_Camera camera = m_view->Camera(); diff --git a/src/gui/v3d_view_camera_animation.h b/src/gui/v3d_view_camera_animation.h new file mode 100644 index 00000000..0b300664 --- /dev/null +++ b/src/gui/v3d_view_camera_animation.h @@ -0,0 +1,67 @@ +/**************************************************************************** +** Copyright (c) 2021, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/quantity.h" + +#include +#include +#include + +namespace Mayo { + +// Provides interface for animation base mechanism(backend) +class IAnimationBackend { +public: + virtual ~IAnimationBackend() = default; + virtual void setDuration(QuantityTime t) = 0; + virtual bool isRunning() const = 0; + virtual void start() = 0; + virtual void stop() = 0; + virtual double valueForProgress(double p) const = 0; // 'p' in [0,1] + virtual void setTimerCallback(std::function fn) = 0; +}; + +// Provides animation control for 3D view camera +class V3dViewCameraAnimation { +public: + using ViewFunction = std::function; + + V3dViewCameraAnimation(); + ~V3dViewCameraAnimation() = default; + + bool hasBackend() const { return m_backend.get() != nullptr; } + void setBackend(std::unique_ptr anim); + + const Handle_V3d_View& view() const { return m_view; } + void setView(const Handle_V3d_View& view); + + QuantityTime duration() const { return m_duration; } + void setDuration(QuantityTime t); + + bool isRunning() const; + void start(); + void stop(); + + void setCameraStart(const Handle_Graphic3d_Camera& camera); + void setCameraEnd(const Handle_Graphic3d_Camera& camera); + void configureCameraChange(const ViewFunction& fnViewChange); + + void setRenderFunction(ViewFunction fnViewRender); + +private: + void updateCurrentTime(QuantityTime currTime); + + std::unique_ptr m_backend; + Handle_V3d_View m_view; + Handle_Graphic3d_Camera m_cameraStart; + Handle_Graphic3d_Camera m_cameraEnd; + QuantityTime m_duration = 1 * Quantity_Second; + std::function m_fnViewRender; +}; + +} // namespace Mayo diff --git a/src/graphics/v3d_view_controller.cpp b/src/gui/v3d_view_controller.cpp similarity index 65% rename from src/graphics/v3d_view_controller.cpp rename to src/gui/v3d_view_controller.cpp index a34d18ea..ea41f46e 100644 --- a/src/graphics/v3d_view_controller.cpp +++ b/src/gui/v3d_view_controller.cpp @@ -5,16 +5,16 @@ ****************************************************************************/ #include "v3d_view_controller.h" +#include "../base/unit_system.h" -#include -#include #include +#include +#include namespace Mayo { -V3dViewController::V3dViewController(const Handle_V3d_View& view, QObject* parent) - : QObject(parent), - m_view(view) +V3dViewController::V3dViewController(const Handle_V3d_View& view) + : m_view(view) { } @@ -22,14 +22,20 @@ void V3dViewController::zoomIn() { m_view->SetScale(m_view->Scale() * 1.1); // +10% this->redrawView(); - emit viewScaled(); + this->signalViewScaled.send(); } void V3dViewController::zoomOut() { m_view->SetScale(m_view->Scale() / 1.1); // -10% this->redrawView(); - emit viewScaled(); + this->signalViewScaled.send(); +} + +void V3dViewController::turn(V3d_TypeOfAxe axis, QuantityAngle angle) +{ + m_view->Turn(axis, UnitSystem::radians(angle), true/*start*/); + this->redrawView(); } void V3dViewController::startDynamicAction(DynamicAction dynAction) @@ -41,13 +47,13 @@ void V3dViewController::startDynamicAction(DynamicAction dynAction) return; m_dynamicAction = dynAction; - emit dynamicActionStarted(dynAction); + this->signalDynamicActionStarted.send(dynAction); } void V3dViewController::stopDynamicAction() { if (m_dynamicAction != DynamicAction::None) { - emit dynamicActionEnded(m_dynamicAction); + this->signalDynamicActionEnded.send(m_dynamicAction); m_dynamicAction = DynamicAction::None; } } @@ -72,22 +78,22 @@ bool V3dViewController::isWindowZoomingStarted() const return m_dynamicAction == DynamicAction::WindowZoom; } -void V3dViewController::rotation(const QPoint& currPos) +void V3dViewController::rotation(const Position& currPos) { if (this->currentDynamicAction() != DynamicAction::Rotation) this->stopDynamicAction(); if (!this->isRotationStarted()) { this->startDynamicAction(DynamicAction::Rotation); - m_view->StartRotation(currPos.x(), currPos.y()); + m_view->StartRotation(currPos.x, currPos.y); } else { - m_view->Rotation(currPos.x(), currPos.y()); + m_view->Rotation(currPos.x, currPos.y); this->redrawView(); } } -void V3dViewController::pan(const QPoint& prevPos, const QPoint& currPos) +void V3dViewController::pan(const Position& prevPos, const Position& currPos) { if (this->currentDynamicAction() != DynamicAction::Panning) this->stopDynamicAction(); @@ -95,32 +101,32 @@ void V3dViewController::pan(const QPoint& prevPos, const QPoint& currPos) if (!this->isPanningStarted()) this->startDynamicAction(DynamicAction::Panning); - m_view->Pan(currPos.x() - prevPos.x(), prevPos.y() - currPos.y()); + m_view->Pan(currPos.x - prevPos.x, prevPos.y - currPos.y); this->redrawView(); } -void V3dViewController::zoom(const QPoint& prevPos, const QPoint& currPos) +void V3dViewController::zoom(const Position& prevPos, const Position& currPos) { if (this->currentDynamicAction() != DynamicAction::Zoom) this->stopDynamicAction(); if (!this->isZoomStarted()) { this->startDynamicAction(DynamicAction::Zoom); - m_view->StartZoomAtPoint(currPos.x(), currPos.y()); + m_view->StartZoomAtPoint(currPos.x, currPos.y); } else { - m_view->Zoom(-prevPos.y(), 0, -currPos.y(), 0); // Zoom by vertical movement + m_view->Zoom(-prevPos.y, 0, -currPos.y, 0); // Zoom by vertical movement this->redrawView(); } } -void V3dViewController::windowFitAll(const QPoint& posMin, const QPoint& posMax) +void V3dViewController::windowFitAll(const Position& posMin, const Position& posMax) { - if (std::abs(posMin.x() - posMax.x()) > 1 || std::abs(posMin.y() - posMax.y()) > 1) - m_view->WindowFitAll(posMin.x(), posMin.y(), posMax.x(), posMax.y()); + if (std::abs(posMin.x - posMax.x) > 1 || std::abs(posMin.y - posMax.y) > 1) + m_view->WindowFitAll(posMin.x, posMin.y, posMax.x, posMax.y); } -void V3dViewController::windowZoomRubberBand(const QPoint& currPos) +void V3dViewController::windowZoomRubberBand(const Position& currPos) { if (!this->isWindowZoomingStarted()) { this->startDynamicAction(DynamicAction::WindowZoom); @@ -130,19 +136,19 @@ void V3dViewController::windowZoomRubberBand(const QPoint& currPos) this->drawRubberBand(m_posRubberBandStart, currPos); } -void V3dViewController::windowZoom(const QPoint& currPos) +void V3dViewController::windowZoom(const Position& currPos) { this->windowFitAll(m_posRubberBandStart, currPos); this->hideRubberBand(); } -void V3dViewController::startInstantZoom(const QPoint& currPos) +void V3dViewController::startInstantZoom(const Position& currPos) { this->startDynamicAction(DynamicAction::InstantZoom); this->backupCamera(); const int dX = m_instantZoomFactor * 100; - m_view->StartZoomAtPoint(currPos.x(), currPos.y()); - m_view->ZoomAtPoint(currPos.x(), currPos.y(), currPos.x() + dX, currPos.y()); + m_view->StartZoomAtPoint(currPos.x, currPos.y); + m_view->ZoomAtPoint(currPos.x, currPos.y, currPos.x + dX, currPos.y); this->redrawView(); } @@ -153,17 +159,16 @@ void V3dViewController::stopInstantZoom() this->redrawView(); } -void V3dViewController::drawRubberBand(const QPoint& posMin, const QPoint& posMax) +void V3dViewController::drawRubberBand(const Position& posMin, const Position& posMax) { if (!m_rubberBand) m_rubberBand = this->createRubberBand(); - QRect rect; - rect.setX(std::min(posMin.x(), posMax.x())); - rect.setY(std::min(posMin.y(), posMax.y())); - rect.setWidth(std::abs(posMax.x() - posMin.x())); - rect.setHeight(std::abs(posMax.y() - posMin.y())); - m_rubberBand->updateGeometry(rect); + const int xRect = std::min(posMin.x, posMax.x); + const int yRect = std::min(posMin.y, posMax.y); + const int width = std::abs(posMax.x - posMin.x); + const int height = std::abs(posMax.y - posMin.y); + m_rubberBand->updateGeometry(xRect, yRect, width, height); m_rubberBand->setVisible(true); } diff --git a/src/graphics/v3d_view_controller.h b/src/gui/v3d_view_controller.h similarity index 51% rename from src/graphics/v3d_view_controller.h rename to src/gui/v3d_view_controller.h index fd984abb..f89a636f 100644 --- a/src/graphics/v3d_view_controller.h +++ b/src/gui/v3d_view_controller.h @@ -6,16 +6,17 @@ #pragma once +#include "../base/quantity.h" +#include "../base/signal.h" +#include "gui_vkey_mouse.h" + #include #include -#include -#include #include namespace Mayo { -class V3dViewController : public QObject { - Q_OBJECT +class V3dViewController { public: enum class DynamicAction { None, @@ -26,13 +27,13 @@ class V3dViewController : public QObject { InstantZoom }; - struct AbstractRubberBand { - virtual ~AbstractRubberBand() {} - virtual void updateGeometry(const QRect& rect) = 0; + struct IRubberBand { + virtual ~IRubberBand() {} + virtual void updateGeometry(int x, int y, int width, int height) = 0; virtual void setVisible(bool on) = 0; }; - V3dViewController(const Handle_V3d_View& view, QObject* parent = nullptr); + V3dViewController(const Handle_V3d_View& view); virtual ~V3dViewController() = default; DynamicAction currentDynamicAction() const; @@ -41,18 +42,24 @@ class V3dViewController : public QObject { void zoomIn(); void zoomOut(); + // Rotates the view point around an axis of frame of reference for which the origin is the + // eye of the projection + void turn(V3d_TypeOfAxe axis, QuantityAngle angle); + double instantZoomFactor() const { return m_instantZoomFactor; } void setInstantZoomFactor(double factor) { m_instantZoomFactor = factor; } -signals: - void dynamicActionStarted(DynamicAction dynAction); - void dynamicActionEnded(DynamicAction dynAction); - void viewScaled(); - - void mouseMoved(const QPoint& posMouseInView); - void mouseClicked(Qt::MouseButton btn); + // Signals + Signal signalDynamicActionStarted; + Signal signalDynamicActionEnded; + Signal<> signalViewScaled; + Signal signalMouseMoved; // x,y: mouse position in view + Signal signalMouseButtonClicked; + Signal signalMultiSelectionToggled; protected: + struct Position { int x; int y; }; + virtual void startDynamicAction(DynamicAction dynAction); virtual void stopDynamicAction(); @@ -61,20 +68,20 @@ class V3dViewController : public QObject { bool isZoomStarted() const; bool isWindowZoomingStarted() const; - void rotation(const QPoint& currPos); - void pan(const QPoint& prevPos, const QPoint& currPos); - void zoom(const QPoint& prevPos, const QPoint& currPos); + void rotation(const Position& currPos); + void pan(const Position& prevPos, const Position& currPos); + void zoom(const Position& prevPos, const Position& currPos); - void windowFitAll(const QPoint& posMin, const QPoint& posMax); + void windowFitAll(const Position& posMin, const Position& posMax); - void windowZoomRubberBand(const QPoint& currPos); - void windowZoom(const QPoint& currPos); + void windowZoomRubberBand(const Position& currPos); + void windowZoom(const Position& currPos); - void startInstantZoom(const QPoint& currPos); + void startInstantZoom(const Position& currPos); void stopInstantZoom(); - virtual std::unique_ptr createRubberBand() = 0; - void drawRubberBand(const QPoint& posMin, const QPoint& posMax); + virtual std::unique_ptr createRubberBand() = 0; + void drawRubberBand(const Position& posMin, const Position& posMax); void hideRubberBand(); void backupCamera(); @@ -85,10 +92,10 @@ class V3dViewController : public QObject { private: Handle_V3d_View m_view; DynamicAction m_dynamicAction = DynamicAction::None; - std::unique_ptr m_rubberBand; + std::unique_ptr m_rubberBand; double m_instantZoomFactor = 5.; Handle_Graphic3d_Camera m_cameraBackup; - QPoint m_posRubberBandStart; + Position m_posRubberBandStart = {}; }; } // namespace Mayo diff --git a/src/io_dxf/dxf.cpp b/src/io_dxf/dxf.cpp index 048a38fa..730be108 100644 --- a/src/io_dxf/dxf.cpp +++ b/src/io_dxf/dxf.cpp @@ -5,17 +5,17 @@ // MAYO: file initially taken from FreeCad/src/Mod/Import/App/dxf.cpp -- commit #1ac35d2 +#if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES) //required by windows for M_PI definition -#define _USE_MATH_DEFINES +# define _USE_MATH_DEFINES +#endif #include #include -#include +#include "../base/filepath.h" #include "dxf.h" -using namespace std; - namespace { template @@ -35,6 +35,7 @@ Base::Vector3d toVector3d(const double* a) } CDxfWrite::CDxfWrite(const char* filepath) : +m_ofs(filepath, std::ios::out), //TODO: these should probably be parameters in config file //handles: //boilerplate 0 - A00 @@ -52,26 +53,15 @@ m_layerName("none") // start the file m_fail = false; m_version = 12; - m_ofs = new std::ofstream(filepath, ios::out); - m_ssBlock = new std::ostringstream(); - m_ssBlkRecord = new std::ostringstream(); - m_ssEntity = new std::ostringstream(); - m_ssLayer = new std::ostringstream(); - if(!(*m_ofs)){ + if(!m_ofs) m_fail = true; - return; - } - m_ofs->imbue(std::locale("C")); + else + m_ofs.imbue(std::locale::classic()); } CDxfWrite::~CDxfWrite() { - delete m_ofs; - delete m_ssBlock; - delete m_ssBlkRecord; - delete m_ssEntity; - delete m_ssLayer; } void CDxfWrite::init(void) @@ -93,8 +83,8 @@ void CDxfWrite::endRun(void) writeEntitiesSection(); writeObjectsSection(); - (*m_ofs) << " 0" << endl; - (*m_ofs) << "EOF"; + m_ofs << " 0" << std::endl; + m_ofs << "EOF"; } //*************************** @@ -113,15 +103,15 @@ void CDxfWrite::writeHeaderSection(void) #endif //header & version - (*m_ofs) << "999" << endl; - (*m_ofs) << ss.str() << endl; + m_ofs << "999" << std::endl; + m_ofs << ss.str() << std::endl; //static header content ss.str(""); ss.clear(); ss << "header" << m_version << ".rub"; std::string fileSpec = m_dataDir + ss.str(); - (*m_ofs) << getPlateFile(fileSpec); + m_ofs << getPlateFile(fileSpec); } //*************************** @@ -137,7 +127,7 @@ void CDxfWrite::writeClassesSection(void) std::stringstream ss; ss << "classes" << m_version << ".rub"; std::string fileSpec = m_dataDir + ss.str(); - (*m_ofs) << getPlateFile(fileSpec); + m_ofs << getPlateFile(fileSpec); } //*************************** @@ -149,24 +139,24 @@ void CDxfWrite::writeTablesSection(void) std::stringstream ss; ss << "tables1" << m_version << ".rub"; std::string fileSpec = m_dataDir + ss.str(); - (*m_ofs) << getPlateFile(fileSpec); + m_ofs << getPlateFile(fileSpec); - (*m_ofs) << (*m_ssLayer).str(); + m_ofs << m_ssLayer.str(); //static tables section tail end content ss.str(""); ss.clear(); ss << "tables2" << m_version << ".rub"; fileSpec = m_dataDir + ss.str(); - (*m_ofs) << getPlateFile(fileSpec); + m_ofs << getPlateFile(fileSpec); if (m_version > 12) { - (*m_ofs) << (*m_ssBlkRecord).str(); - (*m_ofs) << " 0" << endl; - (*m_ofs) << "ENDTAB" << endl; + m_ofs << m_ssBlkRecord.str(); + m_ofs << " 0" << std::endl; + m_ofs << "ENDTAB" << std::endl; } - (*m_ofs) << " 0" << endl; - (*m_ofs) << "ENDSEC" << endl; + m_ofs << " 0" << std::endl; + m_ofs << "ENDSEC" << std::endl; } //*************************** @@ -175,66 +165,66 @@ void CDxfWrite::writeTablesSection(void) void CDxfWrite::makeLayerTable(void) { std::string tablehash = getLayerHandle(); - (*m_ssLayer) << " 0" << endl; - (*m_ssLayer) << "TABLE" << endl; - (*m_ssLayer) << " 2" << endl; - (*m_ssLayer) << "LAYER" << endl; - (*m_ssLayer) << " 5" << endl; - (*m_ssLayer) << tablehash << endl; + m_ssLayer << " 0" << std::endl; + m_ssLayer << "TABLE" << std::endl; + m_ssLayer << " 2" << std::endl; + m_ssLayer << "LAYER" << std::endl; + m_ssLayer << " 5" << std::endl; + m_ssLayer << tablehash << std::endl; if (m_version > 12) { - (*m_ssLayer) << "330" << endl; - (*m_ssLayer) << 0 << endl; - (*m_ssLayer) << "100" << endl; - (*m_ssLayer) << "AcDbSymbolTable" << endl; - } - (*m_ssLayer) << " 70" << endl; - (*m_ssLayer) << m_layerList.size() + 1 << endl; - - (*m_ssLayer) << " 0" << endl; - (*m_ssLayer) << "LAYER" << endl; - (*m_ssLayer) << " 5" << endl; - (*m_ssLayer) << getLayerHandle() << endl; + m_ssLayer << "330" << std::endl; + m_ssLayer << 0 << std::endl; + m_ssLayer << "100" << std::endl; + m_ssLayer << "AcDbSymbolTable" << std::endl; + } + m_ssLayer << " 70" << std::endl; + m_ssLayer << m_layerList.size() + 1 << std::endl; + + m_ssLayer << " 0" << std::endl; + m_ssLayer << "LAYER" << std::endl; + m_ssLayer << " 5" << std::endl; + m_ssLayer << getLayerHandle() << std::endl; if (m_version > 12) { - (*m_ssLayer) << "330" << endl; - (*m_ssLayer) << tablehash << endl; - (*m_ssLayer) << "100" << endl; - (*m_ssLayer) << "AcDbSymbolTableRecord" << endl; - (*m_ssLayer) << "100" << endl; - (*m_ssLayer) << "AcDbLayerTableRecord" << endl; - } - (*m_ssLayer) << " 2" << endl; - (*m_ssLayer) << "0" << endl; - (*m_ssLayer) << " 70" << endl; - (*m_ssLayer) << " 0" << endl; - (*m_ssLayer) << " 62" << endl; - (*m_ssLayer) << " 7" << endl; - (*m_ssLayer) << " 6" << endl; - (*m_ssLayer) << "CONTINUOUS" << endl; + m_ssLayer << "330" << std::endl; + m_ssLayer << tablehash << std::endl; + m_ssLayer << "100" << std::endl; + m_ssLayer << "AcDbSymbolTableRecord" << std::endl; + m_ssLayer << "100" << std::endl; + m_ssLayer << "AcDbLayerTableRecord" << std::endl; + } + m_ssLayer << " 2" << std::endl; + m_ssLayer << "0" << std::endl; + m_ssLayer << " 70" << std::endl; + m_ssLayer << " 0" << std::endl; + m_ssLayer << " 62" << std::endl; + m_ssLayer << " 7" << std::endl; + m_ssLayer << " 6" << std::endl; + m_ssLayer << "CONTINUOUS" << std::endl; for (auto& l: m_layerList) { - (*m_ssLayer) << " 0" << endl; - (*m_ssLayer) << "LAYER" << endl; - (*m_ssLayer) << " 5" << endl; - (*m_ssLayer) << getLayerHandle() << endl; + m_ssLayer << " 0" << std::endl; + m_ssLayer << "LAYER" << std::endl; + m_ssLayer << " 5" << std::endl; + m_ssLayer << getLayerHandle() << std::endl; if (m_version > 12) { - (*m_ssLayer) << "330" << endl; - (*m_ssLayer) << tablehash << endl; - (*m_ssLayer) << "100" << endl; - (*m_ssLayer) << "AcDbSymbolTableRecord" << endl; - (*m_ssLayer) << "100" << endl; - (*m_ssLayer) << "AcDbLayerTableRecord" << endl; + m_ssLayer << "330" << std::endl; + m_ssLayer << tablehash << std::endl; + m_ssLayer << "100" << std::endl; + m_ssLayer << "AcDbSymbolTableRecord" << std::endl; + m_ssLayer << "100" << std::endl; + m_ssLayer << "AcDbLayerTableRecord" << std::endl; } - (*m_ssLayer) << " 2" << endl; - (*m_ssLayer) << l << endl; - (*m_ssLayer) << " 70" << endl; - (*m_ssLayer) << " 0" << endl; - (*m_ssLayer) << " 62" << endl; - (*m_ssLayer) << " 7" << endl; - (*m_ssLayer) << " 6" << endl; - (*m_ssLayer) << "CONTINUOUS" << endl; + m_ssLayer << " 2" << std::endl; + m_ssLayer << l << std::endl; + m_ssLayer << " 70" << std::endl; + m_ssLayer << " 0" << std::endl; + m_ssLayer << " 62" << std::endl; + m_ssLayer << " 7" << std::endl; + m_ssLayer << " 6" << std::endl; + m_ssLayer << "CONTINUOUS" << std::endl; } - (*m_ssLayer) << " 0" << endl; - (*m_ssLayer) << "ENDTAB" << endl; + m_ssLayer << " 0" << std::endl; + m_ssLayer << "ENDTAB" << std::endl; } //*************************** @@ -247,50 +237,50 @@ void CDxfWrite::makeBlockRecordTableHead(void) } std::string tablehash = getBlkRecordHandle(); m_saveBlockRecordTableHandle = tablehash; - (*m_ssBlkRecord) << " 0" << endl; - (*m_ssBlkRecord) << "TABLE" << endl; - (*m_ssBlkRecord) << " 2" << endl; - (*m_ssBlkRecord) << "BLOCK_RECORD" << endl; - (*m_ssBlkRecord) << " 5" << endl; - (*m_ssBlkRecord) << tablehash << endl; - (*m_ssBlkRecord) << "330" << endl; - (*m_ssBlkRecord) << "0" << endl; - (*m_ssBlkRecord) << "100" << endl; - (*m_ssBlkRecord) << "AcDbSymbolTable" << endl; - (*m_ssBlkRecord) << " 70" << endl; - (*m_ssBlkRecord) << (m_blockList.size() + 5) << endl; + m_ssBlkRecord << " 0" << std::endl; + m_ssBlkRecord << "TABLE" << std::endl; + m_ssBlkRecord << " 2" << std::endl; + m_ssBlkRecord << "BLOCK_RECORD" << std::endl; + m_ssBlkRecord << " 5" << std::endl; + m_ssBlkRecord << tablehash << std::endl; + m_ssBlkRecord << "330" << std::endl; + m_ssBlkRecord << "0" << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbSymbolTable" << std::endl; + m_ssBlkRecord << " 70" << std::endl; + m_ssBlkRecord << (m_blockList.size() + 5) << std::endl; m_saveModelSpaceHandle = getBlkRecordHandle(); - (*m_ssBlkRecord) << " 0" << endl; - (*m_ssBlkRecord) << "BLOCK_RECORD" << endl; - (*m_ssBlkRecord) << " 5" << endl; - (*m_ssBlkRecord) << m_saveModelSpaceHandle << endl; - (*m_ssBlkRecord) << "330" << endl; - (*m_ssBlkRecord) << tablehash << endl; - (*m_ssBlkRecord) << "100" << endl; - (*m_ssBlkRecord) << "AcDbSymbolTableRecord" << endl; - (*m_ssBlkRecord) << "100" << endl; - (*m_ssBlkRecord) << "AcDbBlockTableRecord" << endl; - (*m_ssBlkRecord) << " 2" << endl; - (*m_ssBlkRecord) << "*MODEL_SPACE" << endl; -// (*m_ssBlkRecord) << " 1" << endl; -// (*m_ssBlkRecord) << " " << endl; + m_ssBlkRecord << " 0" << std::endl; + m_ssBlkRecord << "BLOCK_RECORD" << std::endl; + m_ssBlkRecord << " 5" << std::endl; + m_ssBlkRecord << m_saveModelSpaceHandle << std::endl; + m_ssBlkRecord << "330" << std::endl; + m_ssBlkRecord << tablehash << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbSymbolTableRecord" << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; + m_ssBlkRecord << " 2" << std::endl; + m_ssBlkRecord << "*MODEL_SPACE" << std::endl; +// m_ssBlkRecord << " 1" << std::endl; +// m_ssBlkRecord << " " << std::endl; m_savePaperSpaceHandle = getBlkRecordHandle(); - (*m_ssBlkRecord) << " 0" << endl; - (*m_ssBlkRecord) << "BLOCK_RECORD" << endl; - (*m_ssBlkRecord) << " 5" << endl; - (*m_ssBlkRecord) << m_savePaperSpaceHandle << endl; - (*m_ssBlkRecord) << "330" << endl; - (*m_ssBlkRecord) << tablehash << endl; - (*m_ssBlkRecord) << "100" << endl; - (*m_ssBlkRecord) << "AcDbSymbolTableRecord" << endl; - (*m_ssBlkRecord) << "100" << endl; - (*m_ssBlkRecord) << "AcDbBlockTableRecord" << endl; - (*m_ssBlkRecord) << " 2" << endl; - (*m_ssBlkRecord) << "*PAPER_SPACE" << endl; -// (*m_ssBlkRecord) << " 1" << endl; -// (*m_ssBlkRecord) << " " << endl; + m_ssBlkRecord << " 0" << std::endl; + m_ssBlkRecord << "BLOCK_RECORD" << std::endl; + m_ssBlkRecord << " 5" << std::endl; + m_ssBlkRecord << m_savePaperSpaceHandle << std::endl; + m_ssBlkRecord << "330" << std::endl; + m_ssBlkRecord << tablehash << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbSymbolTableRecord" << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; + m_ssBlkRecord << " 2" << std::endl; + m_ssBlkRecord << "*PAPER_SPACE" << std::endl; +// m_ssBlkRecord << " 1" << std::endl; +// m_ssBlkRecord << " " << std::endl; } //*************************** @@ -304,20 +294,20 @@ void CDxfWrite::makeBlockRecordTableBody(void) int iBlkRecord = 0; for (auto& b: m_blockList) { - (*m_ssBlkRecord) << " 0" << endl; - (*m_ssBlkRecord) << "BLOCK_RECORD" << endl; - (*m_ssBlkRecord) << " 5" << endl; - (*m_ssBlkRecord) << m_blkRecordList.at(iBlkRecord) << endl; - (*m_ssBlkRecord) << "330" << endl; - (*m_ssBlkRecord) << m_saveBlockRecordTableHandle << endl; - (*m_ssBlkRecord) << "100" << endl; - (*m_ssBlkRecord) << "AcDbSymbolTableRecord" << endl; - (*m_ssBlkRecord) << "100" << endl; - (*m_ssBlkRecord) << "AcDbBlockTableRecord" << endl; - (*m_ssBlkRecord) << " 2" << endl; - (*m_ssBlkRecord) << b << endl; -// (*m_ssBlkRecord) << " 70" << endl; -// (*m_ssBlkRecord) << " 0" << endl; + m_ssBlkRecord << " 0" << std::endl; + m_ssBlkRecord << "BLOCK_RECORD" << std::endl; + m_ssBlkRecord << " 5" << std::endl; + m_ssBlkRecord << m_blkRecordList.at(iBlkRecord) << std::endl; + m_ssBlkRecord << "330" << std::endl; + m_ssBlkRecord << m_saveBlockRecordTableHandle << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbSymbolTableRecord" << std::endl; + m_ssBlkRecord << "100" << std::endl; + m_ssBlkRecord << "AcDbBlockTableRecord" << std::endl; + m_ssBlkRecord << " 2" << std::endl; + m_ssBlkRecord << b << std::endl; +// m_ssBlkRecord << " 70" << std::endl; +// m_ssBlkRecord << " 0" << std::endl; iBlkRecord++; } } @@ -327,124 +317,124 @@ void CDxfWrite::makeBlockRecordTableBody(void) //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project void CDxfWrite::makeBlockSectionHead(void) { - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "SECTION" << endl; - (*m_ssBlock) << " 2" << endl; - (*m_ssBlock) << "BLOCKS" << endl; - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "BLOCK" << endl; - (*m_ssBlock) << " 5" << endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "SECTION" << std::endl; + m_ssBlock << " 2" << std::endl; + m_ssBlock << "BLOCKS" << std::endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "BLOCK" << std::endl; + m_ssBlock << " 5" << std::endl; m_currentBlock = getBlockHandle(); - (*m_ssBlock) << m_currentBlock << endl; + m_ssBlock << m_currentBlock << std::endl; if (m_version > 12) { - (*m_ssBlock) << "330" << endl; - (*m_ssBlock) << m_saveModelSpaceHandle << endl; - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbEntity" << endl; + m_ssBlock << "330" << std::endl; + m_ssBlock << m_saveModelSpaceHandle << std::endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbEntity" << std::endl; } - (*m_ssBlock) << " 8" << endl; - (*m_ssBlock) << "0" << endl; + m_ssBlock << " 8" << std::endl; + m_ssBlock << "0" << std::endl; if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbBlockBegin" << endl; - } - (*m_ssBlock) << " 2" << endl; - (*m_ssBlock) << "*MODEL_SPACE" << endl; - (*m_ssBlock) << " 70" << endl; - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << " 10" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 20" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 30" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 3" << endl; - (*m_ssBlock) << "*MODEL_SPACE" << endl; - (*m_ssBlock) << " 1" << endl; - (*m_ssBlock) << " " << endl; - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "ENDBLK" << endl; - (*m_ssBlock) << " 5" << endl; - (*m_ssBlock) << getBlockHandle() << endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbBlockBegin" << std::endl; + } + m_ssBlock << " 2" << std::endl; + m_ssBlock << "*MODEL_SPACE" << std::endl; + m_ssBlock << " 70" << std::endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << " 10" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 20" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 30" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 3" << std::endl; + m_ssBlock << "*MODEL_SPACE" << std::endl; + m_ssBlock << " 1" << std::endl; + m_ssBlock << " " << std::endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "ENDBLK" << std::endl; + m_ssBlock << " 5" << std::endl; + m_ssBlock << getBlockHandle() << std::endl; if (m_version > 12) { - (*m_ssBlock) << "330" << endl; - (*m_ssBlock) << m_saveModelSpaceHandle << endl; - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbEntity" << endl; + m_ssBlock << "330" << std::endl; + m_ssBlock << m_saveModelSpaceHandle << std::endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbEntity" << std::endl; } - (*m_ssBlock) << " 8" << endl; - (*m_ssBlock) << "0" << endl; + m_ssBlock << " 8" << std::endl; + m_ssBlock << "0" << std::endl; if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbBlockEnd" << endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbBlockEnd" << std::endl; } - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "BLOCK" << endl; - (*m_ssBlock) << " 5" << endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "BLOCK" << std::endl; + m_ssBlock << " 5" << std::endl; m_currentBlock = getBlockHandle(); - (*m_ssBlock) << m_currentBlock << endl; + m_ssBlock << m_currentBlock << std::endl; if (m_version > 12) { - (*m_ssBlock) << "330" << endl; - (*m_ssBlock) << m_savePaperSpaceHandle << endl; - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbEntity" << endl; - (*m_ssBlock) << " 67" << endl; - (*m_ssBlock) << "1" << endl; - } - (*m_ssBlock) << " 8" << endl; - (*m_ssBlock) << "0" << endl; + m_ssBlock << "330" << std::endl; + m_ssBlock << m_savePaperSpaceHandle << std::endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbEntity" << std::endl; + m_ssBlock << " 67" << std::endl; + m_ssBlock << "1" << std::endl; + } + m_ssBlock << " 8" << std::endl; + m_ssBlock << "0" << std::endl; if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbBlockBegin" << endl; - } - (*m_ssBlock) << " 2" << endl; - (*m_ssBlock) << "*PAPER_SPACE" << endl; - (*m_ssBlock) << " 70" << endl; - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << " 10" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 20" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 30" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 3" << endl; - (*m_ssBlock) << "*PAPER_SPACE" << endl; - (*m_ssBlock) << " 1" << endl; - (*m_ssBlock) << " " << endl; - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "ENDBLK" << endl; - (*m_ssBlock) << " 5" << endl; - (*m_ssBlock) << getBlockHandle() << endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbBlockBegin" << std::endl; + } + m_ssBlock << " 2" << std::endl; + m_ssBlock << "*PAPER_SPACE" << std::endl; + m_ssBlock << " 70" << std::endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << " 10" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 20" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 30" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 3" << std::endl; + m_ssBlock << "*PAPER_SPACE" << std::endl; + m_ssBlock << " 1" << std::endl; + m_ssBlock << " " << std::endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "ENDBLK" << std::endl; + m_ssBlock << " 5" << std::endl; + m_ssBlock << getBlockHandle() << std::endl; if (m_version > 12) { - (*m_ssBlock) << "330" << endl; - (*m_ssBlock) << m_savePaperSpaceHandle << endl; - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbEntity" << endl; - (*m_ssBlock) << " 67" << endl; //paper_space flag - (*m_ssBlock) << " 1" << endl; - } - (*m_ssBlock) << " 8" << endl; - (*m_ssBlock) << "0" << endl; + m_ssBlock << "330" << std::endl; + m_ssBlock << m_savePaperSpaceHandle << std::endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbEntity" << std::endl; + m_ssBlock << " 67" << std::endl; //paper_space flag + m_ssBlock << " 1" << std::endl; + } + m_ssBlock << " 8" << std::endl; + m_ssBlock << "0" << std::endl; if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbBlockEnd" << endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbBlockEnd" << std::endl; } } std::string CDxfWrite::getPlateFile(const std::string& fileSpec) { std::stringstream outString; - const std::filesystem::path fpath(fileSpec); - if (!std::filesystem::exists(fpath)) { // TODO Check read permissions + const Mayo::FilePath fpath(fileSpec); + if (!Mayo::filepathExists(fpath)) { // TODO Check read permissions //Base::Console().Message("dxf unable to open %s!\n",fileSpec.c_str()); } else { - string line; - ifstream inFile (fpath); + std::string line; + std::ifstream inFile (fpath); while (!inFile.eof()) { - getline(inFile,line); + std::getline(inFile,line); if (!inFile.eof()) { outString << line << '\n'; } @@ -465,41 +455,21 @@ std::string CDxfWrite::getHandle(void) std::string CDxfWrite::getEntityHandle(void) { return getHandle(); -// m_entityHandle++; -// std::stringstream ss; -// ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); -// ss << m_entityHandle; -// return ss.str(); } std::string CDxfWrite::getLayerHandle(void) { return getHandle(); -// m_layerHandle++; -// std::stringstream ss; -// ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); -// ss << m_layerHandle; -// return ss.str(); } std::string CDxfWrite::getBlockHandle(void) { return getHandle(); -// m_blockHandle++; -// std::stringstream ss; -// ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); -// ss << m_blockHandle; -// return ss.str(); } std::string CDxfWrite::getBlkRecordHandle(void) { return getHandle(); -// m_blkRecordHandle++; -// std::stringstream ss; -// ss << std::uppercase << std::hex << std::setfill('0') << std::setw(2); -// ss << m_blkRecordHandle; -// return ss.str(); } void CDxfWrite::addBlockName(std::string b, std::string h) @@ -520,37 +490,37 @@ void CDxfWrite::writeLine(const double* s, const double* e) } void CDxfWrite::putLine(const Base::Vector3d& s, const Base::Vector3d& e, - std::ostringstream* outStream, const std::string& handle, + std::ostringstream& outStream, const std::string& handle, const std::string& ownerHandle) { - (*outStream) << " 0" << endl; - (*outStream) << "LINE" << endl; - (*outStream) << " 5" << endl; - (*outStream) << handle << endl; + outStream << " 0" << std::endl; + outStream << "LINE" << std::endl; + outStream << " 5" << std::endl; + outStream << handle << std::endl; if (m_version > 12) { - (*outStream) << "330" << endl; - (*outStream) << ownerHandle << endl; - (*outStream) << "100" << endl; - (*outStream) << "AcDbEntity" << endl; + outStream << "330" << std::endl; + outStream << ownerHandle << std::endl; + outStream << "100" << std::endl; + outStream << "AcDbEntity" << std::endl; } - (*outStream) << " 8" << endl; // Group code for layer name - (*outStream) << getLayerName() << endl; // Layer number + outStream << " 8" << std::endl; // Group code for layer name + outStream << getLayerName() << std::endl; // Layer number if (m_version > 12) { - (*outStream) << "100" << endl; - (*outStream) << "AcDbLine" << endl; + outStream << "100" << std::endl; + outStream << "AcDbLine" << std::endl; } - (*outStream) << " 10" << endl; // Start point of line - (*outStream) << s.x << endl; // X in WCS coordinates - (*outStream) << " 20" << endl; - (*outStream) << s.y << endl; // Y in WCS coordinates - (*outStream) << " 30" << endl; - (*outStream) << s.z << endl; // Z in WCS coordinates - (*outStream) << " 11" << endl; // End point of line - (*outStream) << e.x << endl; // X in WCS coordinates - (*outStream) << " 21" << endl; - (*outStream) << e.y << endl; // Y in WCS coordinates - (*outStream) << " 31" << endl; - (*outStream) << e.z << endl; // Z in WCS coordinates + outStream << " 10" << std::endl; // Start point of line + outStream << s.x << std::endl; // X in WCS coordinates + outStream << " 20" << std::endl; + outStream << s.y << std::endl; // Y in WCS coordinates + outStream << " 30" << std::endl; + outStream << s.z << std::endl; // Z in WCS coordinates + outStream << " 11" << std::endl; // End point of line + outStream << e.x << std::endl; // X in WCS coordinates + outStream << " 21" << std::endl; + outStream << e.y << std::endl; // Y in WCS coordinates + outStream << " 31" << std::endl; + outStream << e.z << std::endl; // Z in WCS coordinates } @@ -559,57 +529,57 @@ void CDxfWrite::putLine(const Base::Vector3d& s, const Base::Vector3d& e, //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project void CDxfWrite::writeLWPolyLine(const LWPolyDataOut &pd) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "LWPOLYLINE" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "LWPOLYLINE" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } if (m_version > 12) { - (*m_ssEntity) << "100" << endl; //100 groups are not part of R12 - (*m_ssEntity) << "AcDbPolyline" << endl; - } - (*m_ssEntity) << " 8" << endl; // Group code for layer name - (*m_ssEntity) << getLayerName() << endl; // Layer name - (*m_ssEntity) << " 90" << endl; - (*m_ssEntity) << pd.nVert << endl; // number of vertices - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << pd.Flag << endl; - (*m_ssEntity) << " 43" << endl; - (*m_ssEntity) << "0" << endl; //Constant width opt -// (*m_ssEntity) << pd.Width << endl; //Constant width opt -// (*m_ssEntity) << " 38" << endl; -// (*m_ssEntity) << pd.Elev << endl; // Elevation -// (*m_ssEntity) << " 39" << endl; -// (*m_ssEntity) << pd.Thick << endl; // Thickness + m_ssEntity << "100" << std::endl; //100 groups are not part of R12 + m_ssEntity << "AcDbPolyline" << std::endl; + } + m_ssEntity << " 8" << std::endl; // Group code for layer name + m_ssEntity << getLayerName() << std::endl; // Layer name + m_ssEntity << " 90" << std::endl; + m_ssEntity << pd.nVert << std::endl; // number of vertices + m_ssEntity << " 70" << std::endl; + m_ssEntity << pd.Flag << std::endl; + m_ssEntity << " 43" << std::endl; + m_ssEntity << "0" << std::endl; //Constant width opt +// m_ssEntity << pd.Width << std::endl; //Constant width opt +// m_ssEntity << " 38" << std::endl; +// m_ssEntity << pd.Elev << std::endl; // Elevation +// m_ssEntity << " 39" << std::endl; +// m_ssEntity << pd.Thick << std::endl; // Thickness for (auto& p: pd.Verts) { - (*m_ssEntity) << " 10" << endl; // Vertices - (*m_ssEntity) << p.x << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << p.y << endl; + m_ssEntity << " 10" << std::endl; // Vertices + m_ssEntity << p.x << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << p.y << std::endl; } for (auto& s: pd.StartWidth) { - (*m_ssEntity) << " 40" << endl; - (*m_ssEntity) << s << endl; // Start Width + m_ssEntity << " 40" << std::endl; + m_ssEntity << s << std::endl; // Start Width } for (auto& e: pd.EndWidth) { - (*m_ssEntity) << " 41" << endl; - (*m_ssEntity) << e << endl; // End Width + m_ssEntity << " 41" << std::endl; + m_ssEntity << e << std::endl; // End Width } for (auto& b: pd.Bulge) { // Bulge - (*m_ssEntity) << " 42" << endl; - (*m_ssEntity) << b << endl; + m_ssEntity << " 42" << std::endl; + m_ssEntity << b << std::endl; } -// (*m_ssEntity) << "210" << endl; //Extrusion dir -// (*m_ssEntity) << pd.Extr.x << endl; -// (*m_ssEntity) << "220" << endl; -// (*m_ssEntity) << pd.Extr.y << endl; -// (*m_ssEntity) << "230" << endl; -// (*m_ssEntity) << pd.Extr.z << endl; +// m_ssEntity << "210" << std::endl; //Extrusion dir +// m_ssEntity << pd.Extr.x << std::endl; +// m_ssEntity << "220" << std::endl; +// m_ssEntity << pd.Extr.y << std::endl; +// m_ssEntity << "230" << std::endl; +// m_ssEntity << pd.Extr.z << std::endl; } //*************************** @@ -617,78 +587,78 @@ void CDxfWrite::writeLWPolyLine(const LWPolyDataOut &pd) //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project void CDxfWrite::writePolyline(const LWPolyDataOut &pd) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "POLYLINE" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "POLYLINE" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; // Layer name + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; // Layer name if (m_version > 12) { - (*m_ssEntity) << "100" << endl; //100 groups are not part of R12 - (*m_ssEntity) << "AcDbPolyline" << endl; - } - (*m_ssEntity) << " 66" << endl; - (*m_ssEntity) << " 1" << endl; // vertices follow - (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << "0.0" << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << "0.0" << endl; - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << "0.0" << endl; - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << "0" << endl; + m_ssEntity << "100" << std::endl; //100 groups are not part of R12 + m_ssEntity << "AcDbPolyline" << std::endl; + } + m_ssEntity << " 66" << std::endl; + m_ssEntity << " 1" << std::endl; // vertices follow + m_ssEntity << " 10" << std::endl; + m_ssEntity << "0.0" << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << "0.0" << std::endl; + m_ssEntity << " 30" << std::endl; + m_ssEntity << "0.0" << std::endl; + m_ssEntity << " 70" << std::endl; + m_ssEntity << "0" << std::endl; for (auto& p: pd.Verts) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "VERTEX" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; - (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << p.x << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << p.y << endl; - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << "0.0" << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "VERTEX" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; + m_ssEntity << " 10" << std::endl; + m_ssEntity << p.x << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << p.y << std::endl; + m_ssEntity << " 30" << std::endl; + m_ssEntity << "0.0" << std::endl; } - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "SEQEND" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "SEQEND" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; } void CDxfWrite::writePoint(const double* s) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "POINT" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "POINT" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; // Group code for layer name - (*m_ssEntity) << getLayerName() << endl; // Layer name + m_ssEntity << " 8" << std::endl; // Group code for layer name + m_ssEntity << getLayerName() << std::endl; // Layer name if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbPoint" << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbPoint" << std::endl; } - (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << s[0] << endl; // X in WCS coordinates - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << s[1] << endl; // Y in WCS coordinates - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << s[2] << endl; // Z in WCS coordinates + m_ssEntity << " 10" << std::endl; + m_ssEntity << s[0] << std::endl; // X in WCS coordinates + m_ssEntity << " 20" << std::endl; + m_ssEntity << s[1] << std::endl; // Y in WCS coordinates + m_ssEntity << " 30" << std::endl; + m_ssEntity << s[2] << std::endl; // Z in WCS coordinates } void CDxfWrite::writeArc(const double* s, const double* e, const double* c, bool dir) @@ -706,69 +676,69 @@ void CDxfWrite::writeArc(const double* s, const double* e, const double* c, bool start_angle = end_angle; end_angle = temp; } - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "ARC" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "ARC" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; - } - (*m_ssEntity) << " 8" << endl; // Group code for layer name - (*m_ssEntity) << getLayerName() << endl; // Layer number -// (*m_ssEntity) << " 62" << endl; -// (*m_ssEntity) << " 0" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; + } + m_ssEntity << " 8" << std::endl; // Group code for layer name + m_ssEntity << getLayerName() << std::endl; // Layer number +// m_ssEntity << " 62" << std::endl; +// m_ssEntity << " 0" << std::endl; if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbCircle" << endl; - } - (*m_ssEntity) << " 10" << endl; // Centre X - (*m_ssEntity) << c[0] << endl; // X in WCS coordinates - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << c[1] << endl; // Y in WCS coordinates - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << c[2] << endl; // Z in WCS coordinates - (*m_ssEntity) << " 40" << endl; // - (*m_ssEntity) << radius << endl; // Radius + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbCircle" << std::endl; + } + m_ssEntity << " 10" << std::endl; // Centre X + m_ssEntity << c[0] << std::endl; // X in WCS coordinates + m_ssEntity << " 20" << std::endl; + m_ssEntity << c[1] << std::endl; // Y in WCS coordinates + m_ssEntity << " 30" << std::endl; + m_ssEntity << c[2] << std::endl; // Z in WCS coordinates + m_ssEntity << " 40" << std::endl; // + m_ssEntity << radius << std::endl; // Radius if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbArc" << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbArc" << std::endl; } - (*m_ssEntity) << " 50" << endl; - (*m_ssEntity) << start_angle << endl; // Start angle - (*m_ssEntity) << " 51" << endl; - (*m_ssEntity) << end_angle << endl; // End angle + m_ssEntity << " 50" << std::endl; + m_ssEntity << start_angle << std::endl; // Start angle + m_ssEntity << " 51" << std::endl; + m_ssEntity << end_angle << std::endl; // End angle } void CDxfWrite::writeCircle(const double* c, double radius) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "CIRCLE" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "CIRCLE" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; // Group code for layer name - (*m_ssEntity) << getLayerName() << endl; // Layer number + m_ssEntity << " 8" << std::endl; // Group code for layer name + m_ssEntity << getLayerName() << std::endl; // Layer number if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbCircle" << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbCircle" << std::endl; } - (*m_ssEntity) << " 10" << endl; // Centre X - (*m_ssEntity) << c[0] << endl; // X in WCS coordinates - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << c[1] << endl; // Y in WCS coordinates -// (*m_ssEntity) << " 30" << endl; -// (*m_ssEntity) << c[2] << endl; // Z in WCS coordinates - (*m_ssEntity) << " 40" << endl; // - (*m_ssEntity) << radius << endl; // Radius + m_ssEntity << " 10" << std::endl; // Centre X + m_ssEntity << c[0] << std::endl; // X in WCS coordinates + m_ssEntity << " 20" << std::endl; + m_ssEntity << c[1] << std::endl; // Y in WCS coordinates +// m_ssEntity << " 30" << std::endl; +// m_ssEntity << c[2] << std::endl; // Z in WCS coordinates + m_ssEntity << " 40" << std::endl; // + m_ssEntity << radius << std::endl; // Radius } void CDxfWrite::writeEllipse(const double* c, double major_radius, double minor_radius, @@ -787,46 +757,46 @@ void CDxfWrite::writeEllipse(const double* c, double major_radius, double minor_ start_angle = end_angle; end_angle = temp; } - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "ELLIPSE" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "ELLIPSE" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; // Group code for layer name - (*m_ssEntity) << getLayerName() << endl; // Layer number + m_ssEntity << " 8" << std::endl; // Group code for layer name + m_ssEntity << getLayerName() << std::endl; // Layer number if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEllipse" << endl; - } - (*m_ssEntity) << " 10" << endl; // Centre X - (*m_ssEntity) << c[0] << endl; // X in WCS coordinates - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << c[1] << endl; // Y in WCS coordinates - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << c[2] << endl; // Z in WCS coordinates - (*m_ssEntity) << " 11" << endl; // - (*m_ssEntity) << m[0] << endl; // Major X - (*m_ssEntity) << " 21" << endl; - (*m_ssEntity) << m[1] << endl; // Major Y - (*m_ssEntity) << " 31" << endl; - (*m_ssEntity) << m[2] << endl; // Major Z - (*m_ssEntity) << " 40" << endl; // - (*m_ssEntity) << ratio << endl; // Ratio -// (*m_ssEntity) << "210" << endl; //extrusion dir?? -// (*m_ssEntity) << "0" << endl; -// (*m_ssEntity) << "220" << endl; -// (*m_ssEntity) << "0" << endl; -// (*m_ssEntity) << "230" << endl; -// (*m_ssEntity) << "1" << endl; - (*m_ssEntity) << " 41" << endl; - (*m_ssEntity) << start_angle << endl; // Start angle (radians [0..2pi]) - (*m_ssEntity) << " 42" << endl; - (*m_ssEntity) << end_angle << endl; // End angle + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEllipse" << std::endl; + } + m_ssEntity << " 10" << std::endl; // Centre X + m_ssEntity << c[0] << std::endl; // X in WCS coordinates + m_ssEntity << " 20" << std::endl; + m_ssEntity << c[1] << std::endl; // Y in WCS coordinates + m_ssEntity << " 30" << std::endl; + m_ssEntity << c[2] << std::endl; // Z in WCS coordinates + m_ssEntity << " 11" << std::endl; // + m_ssEntity << m[0] << std::endl; // Major X + m_ssEntity << " 21" << std::endl; + m_ssEntity << m[1] << std::endl; // Major Y + m_ssEntity << " 31" << std::endl; + m_ssEntity << m[2] << std::endl; // Major Z + m_ssEntity << " 40" << std::endl; // + m_ssEntity << ratio << std::endl; // Ratio +// m_ssEntity << "210" << std::endl; //extrusion dir?? +// m_ssEntity << "0" << std::endl; +// m_ssEntity << "220" << std::endl; +// m_ssEntity << "0" << std::endl; +// m_ssEntity << "230" << std::endl; +// m_ssEntity << "1" << std::endl; + m_ssEntity << " 41" << std::endl; + m_ssEntity << start_angle << std::endl; // Start angle (radians [0..2pi]) + m_ssEntity << " 42" << std::endl; + m_ssEntity << end_angle << std::endl; // End angle } //*************************** @@ -834,78 +804,78 @@ void CDxfWrite::writeEllipse(const double* c, double major_radius, double minor_ //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project void CDxfWrite::writeSpline(const SplineDataOut &sd) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "SPLINE" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "SPLINE" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; // Group code for layer name - (*m_ssEntity) << getLayerName() << endl; // Layer name + m_ssEntity << " 8" << std::endl; // Group code for layer name + m_ssEntity << getLayerName() << std::endl; // Layer name if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbSpline" << endl; - } - (*m_ssEntity) << "210" << endl; - (*m_ssEntity) << "0" << endl; - (*m_ssEntity) << "220" << endl; - (*m_ssEntity) << "0" << endl; - (*m_ssEntity) << "230" << endl; - (*m_ssEntity) << "1" << endl; - - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << sd.flag << endl; //flags - (*m_ssEntity) << " 71" << endl; - (*m_ssEntity) << sd.degree << endl; - (*m_ssEntity) << " 72" << endl; - (*m_ssEntity) << sd.knots << endl; - (*m_ssEntity) << " 73" << endl; - (*m_ssEntity) << sd.control_points << endl; - (*m_ssEntity) << " 74" << endl; - (*m_ssEntity) << 0 << endl; - -// (*m_ssEntity) << " 12" << endl; -// (*m_ssEntity) << sd.starttan.x << endl; -// (*m_ssEntity) << " 22" << endl; -// (*m_ssEntity) << sd.starttan.y << endl; -// (*m_ssEntity) << " 32" << endl; -// (*m_ssEntity) << sd.starttan.z << endl; -// (*m_ssEntity) << " 13" << endl; -// (*m_ssEntity) << sd.endtan.x << endl; -// (*m_ssEntity) << " 23" << endl; -// (*m_ssEntity) << sd.endtan.y << endl; -// (*m_ssEntity) << " 33" << endl; -// (*m_ssEntity) << sd.endtan.z << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbSpline" << std::endl; + } + m_ssEntity << "210" << std::endl; + m_ssEntity << "0" << std::endl; + m_ssEntity << "220" << std::endl; + m_ssEntity << "0" << std::endl; + m_ssEntity << "230" << std::endl; + m_ssEntity << "1" << std::endl; + + m_ssEntity << " 70" << std::endl; + m_ssEntity << sd.flag << std::endl; //flags + m_ssEntity << " 71" << std::endl; + m_ssEntity << sd.degree << std::endl; + m_ssEntity << " 72" << std::endl; + m_ssEntity << sd.knots << std::endl; + m_ssEntity << " 73" << std::endl; + m_ssEntity << sd.control_points << std::endl; + m_ssEntity << " 74" << std::endl; + m_ssEntity << 0 << std::endl; + +// m_ssEntity << " 12" << std::endl; +// m_ssEntity << sd.starttan.x << std::endl; +// m_ssEntity << " 22" << std::endl; +// m_ssEntity << sd.starttan.y << std::endl; +// m_ssEntity << " 32" << std::endl; +// m_ssEntity << sd.starttan.z << std::endl; +// m_ssEntity << " 13" << std::endl; +// m_ssEntity << sd.endtan.x << std::endl; +// m_ssEntity << " 23" << std::endl; +// m_ssEntity << sd.endtan.y << std::endl; +// m_ssEntity << " 33" << std::endl; +// m_ssEntity << sd.endtan.z << std::endl; for (auto& k: sd.knot) { - (*m_ssEntity) << " 40" << endl; - (*m_ssEntity) << k << endl; + m_ssEntity << " 40" << std::endl; + m_ssEntity << k << std::endl; } for (auto& w : sd.weight) { - (*m_ssEntity) << " 41" << endl; - (*m_ssEntity) << w << endl; + m_ssEntity << " 41" << std::endl; + m_ssEntity << w << std::endl; } for (auto& c: sd.control) { - (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << c.x << endl; // X in WCS coordinates - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << c.y << endl; // Y in WCS coordinates - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << c.z << endl; // Z in WCS coordinates + m_ssEntity << " 10" << std::endl; + m_ssEntity << c.x << std::endl; // X in WCS coordinates + m_ssEntity << " 20" << std::endl; + m_ssEntity << c.y << std::endl; // Y in WCS coordinates + m_ssEntity << " 30" << std::endl; + m_ssEntity << c.z << std::endl; // Z in WCS coordinates } for (auto& f: sd.fit) { - (*m_ssEntity) << " 11" << endl; - (*m_ssEntity) << f.x << endl; // X in WCS coordinates - (*m_ssEntity) << " 21" << endl; - (*m_ssEntity) << f.y << endl; // Y in WCS coordinates - (*m_ssEntity) << " 31" << endl; - (*m_ssEntity) << f.z << endl; // Z in WCS coordinates + m_ssEntity << " 11" << std::endl; + m_ssEntity << f.x << std::endl; // X in WCS coordinates + m_ssEntity << " 21" << std::endl; + m_ssEntity << f.y << std::endl; // Y in WCS coordinates + m_ssEntity << " 31" << std::endl; + m_ssEntity << f.z << std::endl; // Z in WCS coordinates } } @@ -914,30 +884,30 @@ void CDxfWrite::writeSpline(const SplineDataOut &sd) //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project void CDxfWrite::writeVertex(double x, double y, double z) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "VERTEX" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "VERTEX" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbVertex" << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbVertex" << std::endl; } - (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << x << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << y << endl; - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << z << endl; - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << 0 << endl; + m_ssEntity << " 10" << std::endl; + m_ssEntity << x << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << y << std::endl; + m_ssEntity << " 30" << std::endl; + m_ssEntity << z << std::endl; + m_ssEntity << " 70" << std::endl; + m_ssEntity << 0 << std::endl; } void CDxfWrite::writeText(const char* text, const double* location1, const double* location2, @@ -953,119 +923,119 @@ void CDxfWrite::writeText(const char* text, const double* location1, const doubl //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project void CDxfWrite::putText(const char* text, const Base::Vector3d& location1, const Base::Vector3d& location2, const double height, const int horizJust, - std::ostringstream* outStream, const std::string& handle, + std::ostringstream& outStream, const std::string& handle, const std::string& ownerHandle) { (void) location2; - (*outStream) << " 0" << endl; - (*outStream) << "TEXT" << endl; - (*outStream) << " 5" << endl; - (*outStream) << handle << endl; + outStream << " 0" << std::endl; + outStream << "TEXT" << std::endl; + outStream << " 5" << std::endl; + outStream << handle << std::endl; if (m_version > 12) { - (*outStream) << "330" << endl; - (*outStream) << ownerHandle << endl; - (*outStream) << "100" << endl; - (*outStream) << "AcDbEntity" << endl; + outStream << "330" << std::endl; + outStream << ownerHandle << std::endl; + outStream << "100" << std::endl; + outStream << "AcDbEntity" << std::endl; } - (*outStream) << " 8" << endl; - (*outStream) << getLayerName() << endl; + outStream << " 8" << std::endl; + outStream << getLayerName() << std::endl; if (m_version > 12) { - (*outStream) << "100" << endl; - (*outStream) << "AcDbText" << endl; - } -// (*outStream) << " 39" << endl; -// (*outStream) << 0 << endl; //thickness - (*outStream) << " 10" << endl; //first alignment point - (*outStream) << location1.x << endl; - (*outStream) << " 20" << endl; - (*outStream) << location1.y << endl; - (*outStream) << " 30" << endl; - (*outStream) << location1.z << endl; - (*outStream) << " 40" << endl; - (*outStream) << height << endl; - (*outStream) << " 1" << endl; - (*outStream) << text << endl; -// (*outStream) << " 50" << endl; -// (*outStream) << 0 << endl; //rotation -// (*outStream) << " 41" << endl; -// (*outStream) << 1 << endl; -// (*outStream) << " 51" << endl; -// (*outStream) << 0 << endl; - - (*outStream) << " 7" << endl; - (*outStream) << "STANDARD" << endl; //style -// (*outStream) << " 71" << endl; //default -// (*outStream) << "0" << endl; - (*outStream) << " 72" << endl; - (*outStream) << horizJust << endl; -//// (*outStream) << " 73" << endl; -//// (*outStream) << "0" << endl; - (*outStream) << " 11" << endl; //second alignment point - (*outStream) << location2.x << endl; - (*outStream) << " 21" << endl; - (*outStream) << location2.y << endl; - (*outStream) << " 31" << endl; - (*outStream) << location2.z << endl; -// (*outStream) << "210" << endl; -// (*outStream) << "0" << endl; -// (*outStream) << "220" << endl; -// (*outStream) << "0" << endl; -// (*outStream) << "230" << endl; -// (*outStream) << "1" << endl; + outStream << "100" << std::endl; + outStream << "AcDbText" << std::endl; + } +// outStream << " 39" << std::endl; +// outStream << 0 << std::endl; //thickness + outStream << " 10" << std::endl; //first alignment point + outStream << location1.x << std::endl; + outStream << " 20" << std::endl; + outStream << location1.y << std::endl; + outStream << " 30" << std::endl; + outStream << location1.z << std::endl; + outStream << " 40" << std::endl; + outStream << height << std::endl; + outStream << " 1" << std::endl; + outStream << text << std::endl; +// outStream << " 50" << std::endl; +// outStream << 0 << std::endl; //rotation +// outStream << " 41" << std::endl; +// outStream << 1 << std::endl; +// outStream << " 51" << std::endl; +// outStream << 0 << std::endl; + + outStream << " 7" << std::endl; + outStream << "STANDARD" << std::endl; //style +// outStream << " 71" << std::endl; //default +// outStream << "0" << std::endl; + outStream << " 72" << std::endl; + outStream << horizJust << std::endl; +//// outStream << " 73" << std::endl; +//// outStream << "0" << std::endl; + outStream << " 11" << std::endl; //second alignment point + outStream << location2.x << std::endl; + outStream << " 21" << std::endl; + outStream << location2.y << std::endl; + outStream << " 31" << std::endl; + outStream << location2.z << std::endl; +// outStream << "210" << std::endl; +// outStream << "0" << std::endl; +// outStream << "220" << std::endl; +// outStream << "0" << std::endl; +// outStream << "230" << std::endl; +// outStream << "1" << std::endl; if (m_version > 12) { - (*outStream) << "100" << endl; - (*outStream) << "AcDbText" << endl; + outStream << "100" << std::endl; + outStream << "AcDbText" << std::endl; } } void CDxfWrite::putArrow(const Base::Vector3d& arrowPos, const Base::Vector3d& barb1Pos, const Base::Vector3d& barb2Pos, - std::ostringstream* outStream, const std::string& handle, + std::ostringstream& outStream, const std::string& handle, const std::string& ownerHandle) { - (*outStream) << " 0" << endl; - (*outStream) << "SOLID" << endl; - (*outStream) << " 5" << endl; - (*outStream) << handle << endl; + outStream << " 0" << std::endl; + outStream << "SOLID" << std::endl; + outStream << " 5" << std::endl; + outStream << handle << std::endl; if (m_version > 12) { - (*outStream) << "330" << endl; - (*outStream) << ownerHandle << endl; - (*outStream) << "100" << endl; - (*outStream) << "AcDbEntity" << endl; - } - (*outStream) << " 8" << endl; - (*outStream) << "0" << endl; - (*outStream) << " 62" << endl; - (*outStream) << " 0" << endl; + outStream << "330" << std::endl; + outStream << ownerHandle << std::endl; + outStream << "100" << std::endl; + outStream << "AcDbEntity" << std::endl; + } + outStream << " 8" << std::endl; + outStream << "0" << std::endl; + outStream << " 62" << std::endl; + outStream << " 0" << std::endl; if (m_version > 12) { - (*outStream) << "100" << endl; - (*outStream) << "AcDbTrace" << endl; - } - (*outStream) << " 10" << endl; - (*outStream) << barb1Pos.x << endl; - (*outStream) << " 20" << endl; - (*outStream) << barb1Pos.y << endl; - (*outStream) << " 30" << endl; - (*outStream) << barb1Pos.z << endl; - (*outStream) << " 11" << endl; - (*outStream) << barb2Pos.x << endl; - (*outStream) << " 21" << endl; - (*outStream) << barb2Pos.y << endl; - (*outStream) << " 31" << endl; - (*outStream) << barb2Pos.z << endl; - (*outStream) << " 12" << endl; - (*outStream) << arrowPos.x << endl; - (*outStream) << " 22" << endl; - (*outStream) << arrowPos.y << endl; - (*outStream) << " 32" << endl; - (*outStream) << arrowPos.z << endl; - (*outStream) << " 13" << endl; - (*outStream) << arrowPos.x << endl; - (*outStream) << " 23" << endl; - (*outStream) << arrowPos.y << endl; - (*outStream) << " 33" << endl; - (*outStream) << arrowPos.z << endl; + outStream << "100" << std::endl; + outStream << "AcDbTrace" << std::endl; + } + outStream << " 10" << std::endl; + outStream << barb1Pos.x << std::endl; + outStream << " 20" << std::endl; + outStream << barb1Pos.y << std::endl; + outStream << " 30" << std::endl; + outStream << barb1Pos.z << std::endl; + outStream << " 11" << std::endl; + outStream << barb2Pos.x << std::endl; + outStream << " 21" << std::endl; + outStream << barb2Pos.y << std::endl; + outStream << " 31" << std::endl; + outStream << barb2Pos.z << std::endl; + outStream << " 12" << std::endl; + outStream << arrowPos.x << std::endl; + outStream << " 22" << std::endl; + outStream << arrowPos.y << std::endl; + outStream << " 32" << std::endl; + outStream << arrowPos.z << std::endl; + outStream << " 13" << std::endl; + outStream << arrowPos.x << std::endl; + outStream << " 23" << std::endl; + outStream << arrowPos.y << std::endl; + outStream << " 33" << std::endl; + outStream << arrowPos.z << std::endl; } //*************************** @@ -1078,77 +1048,77 @@ void CDxfWrite::writeLinearDim(const double* textMidPoint, const double* lineDef const double* extLine1, const double* extLine2, const char* dimText, int type) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "DIMENSION" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "DIMENSION" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbDimension" << endl; - } - (*m_ssEntity) << " 2" << endl; - (*m_ssEntity) << "*" << getLayerName() << endl; // blockName - (*m_ssEntity) << " 10" << endl; //dimension line definition point - (*m_ssEntity) << lineDefPoint[0] << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << lineDefPoint[1] << endl; - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << lineDefPoint[2] << endl; - (*m_ssEntity) << " 11" << endl; //text mid point - (*m_ssEntity) << textMidPoint[0] << endl; - (*m_ssEntity) << " 21" << endl; - (*m_ssEntity) << textMidPoint[1] << endl; - (*m_ssEntity) << " 31" << endl; - (*m_ssEntity) << textMidPoint[2] << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbDimension" << std::endl; + } + m_ssEntity << " 2" << std::endl; + m_ssEntity << "*" << getLayerName() << std::endl; // blockName + m_ssEntity << " 10" << std::endl; //dimension line definition point + m_ssEntity << lineDefPoint[0] << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << lineDefPoint[1] << std::endl; + m_ssEntity << " 30" << std::endl; + m_ssEntity << lineDefPoint[2] << std::endl; + m_ssEntity << " 11" << std::endl; //text mid point + m_ssEntity << textMidPoint[0] << std::endl; + m_ssEntity << " 21" << std::endl; + m_ssEntity << textMidPoint[1] << std::endl; + m_ssEntity << " 31" << std::endl; + m_ssEntity << textMidPoint[2] << std::endl; if (type == ALIGNED) { - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << 1 << endl; // dimType1 = Aligned + m_ssEntity << " 70" << std::endl; + m_ssEntity << 1 << std::endl; // dimType1 = Aligned } if ( (type == HORIZONTAL) || (type == VERTICAL) ) { - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << 32 << endl; // dimType0 = Aligned + 32 (bit for unique block)? - } -// (*m_ssEntity) << " 71" << endl; // not R12 -// (*m_ssEntity) << 1 << endl; // attachPoint ??1 = topleft - (*m_ssEntity) << " 1" << endl; - (*m_ssEntity) << dimText << endl; - (*m_ssEntity) << " 3" << endl; - (*m_ssEntity) << "STANDARD" << endl; //style + m_ssEntity << " 70" << std::endl; + m_ssEntity << 32 << std::endl; // dimType0 = Aligned + 32 (bit for unique block)? + } +// m_ssEntity << " 71" << std::endl; // not R12 +// m_ssEntity << 1 << std::endl; // attachPoint ??1 = topleft + m_ssEntity << " 1" << std::endl; + m_ssEntity << dimText << std::endl; + m_ssEntity << " 3" << std::endl; + m_ssEntity << "STANDARD" << std::endl; //style //linear dims if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbAlignedDimension" << endl; - } - (*m_ssEntity) << " 13" << endl; - (*m_ssEntity) << extLine1[0] << endl; - (*m_ssEntity) << " 23" << endl; - (*m_ssEntity) << extLine1[1] << endl; - (*m_ssEntity) << " 33" << endl; - (*m_ssEntity) << extLine1[2] << endl; - (*m_ssEntity) << " 14" << endl; - (*m_ssEntity) << extLine2[0] << endl; - (*m_ssEntity) << " 24" << endl; - (*m_ssEntity) << extLine2[1] << endl; - (*m_ssEntity) << " 34" << endl; - (*m_ssEntity) << extLine2[2] << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbAlignedDimension" << std::endl; + } + m_ssEntity << " 13" << std::endl; + m_ssEntity << extLine1[0] << std::endl; + m_ssEntity << " 23" << std::endl; + m_ssEntity << extLine1[1] << std::endl; + m_ssEntity << " 33" << std::endl; + m_ssEntity << extLine1[2] << std::endl; + m_ssEntity << " 14" << std::endl; + m_ssEntity << extLine2[0] << std::endl; + m_ssEntity << " 24" << std::endl; + m_ssEntity << extLine2[1] << std::endl; + m_ssEntity << " 34" << std::endl; + m_ssEntity << extLine2[2] << std::endl; if (m_version > 12) { if (type == VERTICAL) { - (*m_ssEntity) << " 50" << endl; - (*m_ssEntity) << "90" << endl; + m_ssEntity << " 50" << std::endl; + m_ssEntity << "90" << std::endl; } if ( (type == HORIZONTAL) || (type == VERTICAL) ) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbRotatedDimension" << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbRotatedDimension" << std::endl; } } @@ -1167,80 +1137,80 @@ void CDxfWrite::writeAngularDim(const double* textMidPoint, const double* lineDe const double* startExt2, const double* endExt2, const char* dimText) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "DIMENSION" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "DIMENSION" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbDimension" << endl; - } - (*m_ssEntity) << " 2" << endl; - (*m_ssEntity) << "*" << getLayerName() << endl; // blockName - - (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << endExt2[0] << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << endExt2[1] << endl; - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << endExt2[2] << endl; - - (*m_ssEntity) << " 11" << endl; - (*m_ssEntity) << textMidPoint[0] << endl; - (*m_ssEntity) << " 21" << endl; - (*m_ssEntity) << textMidPoint[1] << endl; - (*m_ssEntity) << " 31" << endl; - (*m_ssEntity) << textMidPoint[2] << endl; - - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << 2 << endl; // dimType 2 = Angular 5 = Angular 3 point + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbDimension" << std::endl; + } + m_ssEntity << " 2" << std::endl; + m_ssEntity << "*" << getLayerName() << std::endl; // blockName + + m_ssEntity << " 10" << std::endl; + m_ssEntity << endExt2[0] << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << endExt2[1] << std::endl; + m_ssEntity << " 30" << std::endl; + m_ssEntity << endExt2[2] << std::endl; + + m_ssEntity << " 11" << std::endl; + m_ssEntity << textMidPoint[0] << std::endl; + m_ssEntity << " 21" << std::endl; + m_ssEntity << textMidPoint[1] << std::endl; + m_ssEntity << " 31" << std::endl; + m_ssEntity << textMidPoint[2] << std::endl; + + m_ssEntity << " 70" << std::endl; + m_ssEntity << 2 << std::endl; // dimType 2 = Angular 5 = Angular 3 point // +32 for block?? (not R12) -// (*m_ssEntity) << " 71" << endl; // not R12? not required? -// (*m_ssEntity) << 5 << endl; // attachPoint 5 = middle - (*m_ssEntity) << " 1" << endl; - (*m_ssEntity) << dimText << endl; - (*m_ssEntity) << " 3" << endl; - (*m_ssEntity) << "STANDARD" << endl; //style +// m_ssEntity << " 71" << std::endl; // not R12? not required? +// m_ssEntity << 5 << std::endl; // attachPoint 5 = middle + m_ssEntity << " 1" << std::endl; + m_ssEntity << dimText << std::endl; + m_ssEntity << " 3" << std::endl; + m_ssEntity << "STANDARD" << std::endl; //style //angular dims if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDb2LineAngularDimension" << endl; - } - (*m_ssEntity) << " 13" << endl; - (*m_ssEntity) << startExt1[0] << endl; - (*m_ssEntity) << " 23" << endl; - (*m_ssEntity) << startExt1[1] << endl; - (*m_ssEntity) << " 33" << endl; - (*m_ssEntity) << startExt1[2] << endl; - - (*m_ssEntity) << " 14" << endl; - (*m_ssEntity) << endExt1[0] << endl; - (*m_ssEntity) << " 24" << endl; - (*m_ssEntity) << endExt1[1] << endl; - (*m_ssEntity) << " 34" << endl; - (*m_ssEntity) << endExt1[2] << endl; - - (*m_ssEntity) << " 15" << endl; - (*m_ssEntity) << startExt2[0] << endl; - (*m_ssEntity) << " 25" << endl; - (*m_ssEntity) << startExt2[1] << endl; - (*m_ssEntity) << " 35" << endl; - (*m_ssEntity) << startExt2[2] << endl; - - (*m_ssEntity) << " 16" << endl; - (*m_ssEntity) << lineDefPoint[0] << endl; - (*m_ssEntity) << " 26" << endl; - (*m_ssEntity) << lineDefPoint[1] << endl; - (*m_ssEntity) << " 36" << endl; - (*m_ssEntity) << lineDefPoint[2] << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDb2LineAngularDimension" << std::endl; + } + m_ssEntity << " 13" << std::endl; + m_ssEntity << startExt1[0] << std::endl; + m_ssEntity << " 23" << std::endl; + m_ssEntity << startExt1[1] << std::endl; + m_ssEntity << " 33" << std::endl; + m_ssEntity << startExt1[2] << std::endl; + + m_ssEntity << " 14" << std::endl; + m_ssEntity << endExt1[0] << std::endl; + m_ssEntity << " 24" << std::endl; + m_ssEntity << endExt1[1] << std::endl; + m_ssEntity << " 34" << std::endl; + m_ssEntity << endExt1[2] << std::endl; + + m_ssEntity << " 15" << std::endl; + m_ssEntity << startExt2[0] << std::endl; + m_ssEntity << " 25" << std::endl; + m_ssEntity << startExt2[1] << std::endl; + m_ssEntity << " 35" << std::endl; + m_ssEntity << startExt2[2] << std::endl; + + m_ssEntity << " 16" << std::endl; + m_ssEntity << lineDefPoint[0] << std::endl; + m_ssEntity << " 26" << std::endl; + m_ssEntity << lineDefPoint[1] << std::endl; + m_ssEntity << " 36" << std::endl; + m_ssEntity << lineDefPoint[2] << std::endl; writeDimBlockPreamble(); writeAngularDimBlock(textMidPoint, lineDefPoint, startExt1, endExt1, @@ -1256,57 +1226,57 @@ void CDxfWrite::writeRadialDim(const double* centerPoint, const double* textMidP const double* arcPoint, const char* dimText) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "DIMENSION" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "DIMENSION" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbDimension" << endl; - } - (*m_ssEntity) << " 2" << endl; - (*m_ssEntity) << "*" << getLayerName() << endl; // blockName - (*m_ssEntity) << " 10" << endl; // arc center point - (*m_ssEntity) << centerPoint[0] << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << centerPoint[1] << endl; - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << centerPoint[2] << endl; - (*m_ssEntity) << " 11" << endl; //text mid point - (*m_ssEntity) << textMidPoint[0] << endl; - (*m_ssEntity) << " 21" << endl; - (*m_ssEntity) << textMidPoint[1] << endl; - (*m_ssEntity) << " 31" << endl; - (*m_ssEntity) << textMidPoint[2] << endl; - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << 4 << endl; // dimType 4 = Radius -// (*m_ssEntity) << " 71" << endl; // not R12 -// (*m_ssEntity) << 1 << endl; // attachPoint 5 = middle center - (*m_ssEntity) << " 1" << endl; - (*m_ssEntity) << dimText << endl; - (*m_ssEntity) << " 3" << endl; - (*m_ssEntity) << "STANDARD" << endl; //style + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbDimension" << std::endl; + } + m_ssEntity << " 2" << std::endl; + m_ssEntity << "*" << getLayerName() << std::endl; // blockName + m_ssEntity << " 10" << std::endl; // arc center point + m_ssEntity << centerPoint[0] << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << centerPoint[1] << std::endl; + m_ssEntity << " 30" << std::endl; + m_ssEntity << centerPoint[2] << std::endl; + m_ssEntity << " 11" << std::endl; //text mid point + m_ssEntity << textMidPoint[0] << std::endl; + m_ssEntity << " 21" << std::endl; + m_ssEntity << textMidPoint[1] << std::endl; + m_ssEntity << " 31" << std::endl; + m_ssEntity << textMidPoint[2] << std::endl; + m_ssEntity << " 70" << std::endl; + m_ssEntity << 4 << std::endl; // dimType 4 = Radius +// m_ssEntity << " 71" << std::endl; // not R12 +// m_ssEntity << 1 << std::endl; // attachPoint 5 = middle center + m_ssEntity << " 1" << std::endl; + m_ssEntity << dimText << std::endl; + m_ssEntity << " 3" << std::endl; + m_ssEntity << "STANDARD" << std::endl; //style //radial dims if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbRadialDimension" << endl; - } - (*m_ssEntity) << " 15" << endl; - (*m_ssEntity) << arcPoint[0] << endl; - (*m_ssEntity) << " 25" << endl; - (*m_ssEntity) << arcPoint[1] << endl; - (*m_ssEntity) << " 35" << endl; - (*m_ssEntity) << arcPoint[2] << endl; - (*m_ssEntity) << " 40" << endl; // leader length???? - (*m_ssEntity) << 0 << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbRadialDimension" << std::endl; + } + m_ssEntity << " 15" << std::endl; + m_ssEntity << arcPoint[0] << std::endl; + m_ssEntity << " 25" << std::endl; + m_ssEntity << arcPoint[1] << std::endl; + m_ssEntity << " 35" << std::endl; + m_ssEntity << arcPoint[2] << std::endl; + m_ssEntity << " 40" << std::endl; // leader length???? + m_ssEntity << 0 << std::endl; writeDimBlockPreamble(); writeRadialDimBlock(centerPoint, textMidPoint, arcPoint, dimText); @@ -1320,57 +1290,57 @@ void CDxfWrite::writeDiametricDim(const double* textMidPoint, const double* arcPoint1, const double* arcPoint2, const char* dimText) { - (*m_ssEntity) << " 0" << endl; - (*m_ssEntity) << "DIMENSION" << endl; - (*m_ssEntity) << " 5" << endl; - (*m_ssEntity) << getEntityHandle() << endl; + m_ssEntity << " 0" << std::endl; + m_ssEntity << "DIMENSION" << std::endl; + m_ssEntity << " 5" << std::endl; + m_ssEntity << getEntityHandle() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "330" << endl; - (*m_ssEntity) << m_saveModelSpaceHandle << endl; - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbEntity" << endl; + m_ssEntity << "330" << std::endl; + m_ssEntity << m_saveModelSpaceHandle << std::endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbEntity" << std::endl; } - (*m_ssEntity) << " 8" << endl; - (*m_ssEntity) << getLayerName() << endl; + m_ssEntity << " 8" << std::endl; + m_ssEntity << getLayerName() << std::endl; if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbDimension" << endl; - } - (*m_ssEntity) << " 2" << endl; - (*m_ssEntity) << "*" << getLayerName() << endl; // blockName - (*m_ssEntity) << " 10" << endl; - (*m_ssEntity) << arcPoint1[0] << endl; - (*m_ssEntity) << " 20" << endl; - (*m_ssEntity) << arcPoint1[1] << endl; - (*m_ssEntity) << " 30" << endl; - (*m_ssEntity) << arcPoint1[2] << endl; - (*m_ssEntity) << " 11" << endl; //text mid point - (*m_ssEntity) << textMidPoint[0] << endl; - (*m_ssEntity) << " 21" << endl; - (*m_ssEntity) << textMidPoint[1] << endl; - (*m_ssEntity) << " 31" << endl; - (*m_ssEntity) << textMidPoint[2] << endl; - (*m_ssEntity) << " 70" << endl; - (*m_ssEntity) << 3 << endl; // dimType 3 = Diameter -// (*m_ssEntity) << " 71" << endl; // not R12 -// (*m_ssEntity) << 5 << endl; // attachPoint 5 = middle center - (*m_ssEntity) << " 1" << endl; - (*m_ssEntity) << dimText << endl; - (*m_ssEntity) << " 3" << endl; - (*m_ssEntity) << "STANDARD" << endl; //style + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbDimension" << std::endl; + } + m_ssEntity << " 2" << std::endl; + m_ssEntity << "*" << getLayerName() << std::endl; // blockName + m_ssEntity << " 10" << std::endl; + m_ssEntity << arcPoint1[0] << std::endl; + m_ssEntity << " 20" << std::endl; + m_ssEntity << arcPoint1[1] << std::endl; + m_ssEntity << " 30" << std::endl; + m_ssEntity << arcPoint1[2] << std::endl; + m_ssEntity << " 11" << std::endl; //text mid point + m_ssEntity << textMidPoint[0] << std::endl; + m_ssEntity << " 21" << std::endl; + m_ssEntity << textMidPoint[1] << std::endl; + m_ssEntity << " 31" << std::endl; + m_ssEntity << textMidPoint[2] << std::endl; + m_ssEntity << " 70" << std::endl; + m_ssEntity << 3 << std::endl; // dimType 3 = Diameter +// m_ssEntity << " 71" << std::endl; // not R12 +// m_ssEntity << 5 << std::endl; // attachPoint 5 = middle center + m_ssEntity << " 1" << std::endl; + m_ssEntity << dimText << std::endl; + m_ssEntity << " 3" << std::endl; + m_ssEntity << "STANDARD" << std::endl; //style //diametric dims if (m_version > 12) { - (*m_ssEntity) << "100" << endl; - (*m_ssEntity) << "AcDbDiametricDimension" << endl; - } - (*m_ssEntity) << " 15" << endl; - (*m_ssEntity) << arcPoint2[0] << endl; - (*m_ssEntity) << " 25" << endl; - (*m_ssEntity) << arcPoint2[1] << endl; - (*m_ssEntity) << " 35" << endl; - (*m_ssEntity) << arcPoint2[2] << endl; - (*m_ssEntity) << " 40" << endl; // leader length???? - (*m_ssEntity) << 0 << endl; + m_ssEntity << "100" << std::endl; + m_ssEntity << "AcDbDiametricDimension" << std::endl; + } + m_ssEntity << " 15" << std::endl; + m_ssEntity << arcPoint2[0] << std::endl; + m_ssEntity << " 25" << std::endl; + m_ssEntity << arcPoint2[1] << std::endl; + m_ssEntity << " 35" << std::endl; + m_ssEntity << arcPoint2[2] << std::endl; + m_ssEntity << " 40" << std::endl; // leader length???? + m_ssEntity << 0 << std::endl; writeDimBlockPreamble(); writeDiametricDimBlock(textMidPoint, arcPoint1, arcPoint2, dimText); @@ -1390,36 +1360,36 @@ void CDxfWrite::writeDimBlockPreamble(void) } m_currentBlock = getBlockHandle(); - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "BLOCK" << endl; - (*m_ssBlock) << " 5" << endl; - (*m_ssBlock) << m_currentBlock << endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "BLOCK" << std::endl; + m_ssBlock << " 5" << std::endl; + m_ssBlock << m_currentBlock << std::endl; if (m_version > 12) { - (*m_ssBlock) << "330" << endl; - (*m_ssBlock) << m_saveBlkRecordHandle << endl; - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbEntity" << endl; + m_ssBlock << "330" << std::endl; + m_ssBlock << m_saveBlkRecordHandle << std::endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbEntity" << std::endl; } - (*m_ssBlock) << " 8" << endl; - (*m_ssBlock) << getLayerName() << endl; + m_ssBlock << " 8" << std::endl; + m_ssBlock << getLayerName() << std::endl; if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbBlockBegin" << endl; - } - (*m_ssBlock) << " 2" << endl; - (*m_ssBlock) << "*" << getLayerName() << endl; // blockName - (*m_ssBlock) << " 70" << endl; - (*m_ssBlock) << " 1" << endl; - (*m_ssBlock) << " 10" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 20" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 30" << endl; - (*m_ssBlock) << 0.0 << endl; - (*m_ssBlock) << " 3" << endl; - (*m_ssBlock) << "*" << getLayerName() << endl; // blockName - (*m_ssBlock) << " 1" << endl; - (*m_ssBlock) << " " << endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbBlockBegin" << std::endl; + } + m_ssBlock << " 2" << std::endl; + m_ssBlock << "*" << getLayerName() << std::endl; // blockName + m_ssBlock << " 70" << std::endl; + m_ssBlock << " 1" << std::endl; + m_ssBlock << " 10" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 20" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 30" << std::endl; + m_ssBlock << 0.0 << std::endl; + m_ssBlock << " 3" << std::endl; + m_ssBlock << "*" << getLayerName() << std::endl; // blockName + m_ssBlock << " 1" << std::endl; + m_ssBlock << " " << std::endl; } //*************************** @@ -1427,23 +1397,23 @@ void CDxfWrite::writeDimBlockPreamble(void) //added by Wandererfan 2018 (wandererfan@gmail.com) for FreeCAD project void CDxfWrite::writeBlockTrailer(void) { - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "ENDBLK" << endl; - (*m_ssBlock) << " 5" << endl; - (*m_ssBlock) << getBlockHandle() << endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "ENDBLK" << std::endl; + m_ssBlock << " 5" << std::endl; + m_ssBlock << getBlockHandle() << std::endl; if (m_version > 12) { - (*m_ssBlock) << "330" << endl; - (*m_ssBlock) << m_saveBlkRecordHandle << endl; - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbEntity" << endl; - } -// (*m_ssBlock) << " 67" << endl; -// (*m_ssBlock) << "1" << endl; - (*m_ssBlock) << " 8" << endl; - (*m_ssBlock) << getLayerName() << endl; + m_ssBlock << "330" << std::endl; + m_ssBlock << m_saveBlkRecordHandle << std::endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbEntity" << std::endl; + } +// m_ssBlock << " 67" << std::endl; +// m_ssBlock << "1" << std::endl; + m_ssBlock << " 8" << std::endl; + m_ssBlock << getLayerName() << std::endl; if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbBlockEnd" << endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbBlockEnd" << std::endl; } } @@ -1565,40 +1535,40 @@ void CDxfWrite::writeAngularDimBlock(const double* textMidPoint, const double* l Base::Vector3d linePt(lineDefPoint[0],lineDefPoint[1],lineDefPoint[2]); double radius = (e2S - linePt).Length(); - (*m_ssBlock) << " 0" << endl; - (*m_ssBlock) << "ARC" << endl; //dimline arc - (*m_ssBlock) << " 5" << endl; - (*m_ssBlock) << getBlockHandle() << endl; + m_ssBlock << " 0" << std::endl; + m_ssBlock << "ARC" << std::endl; //dimline arc + m_ssBlock << " 5" << std::endl; + m_ssBlock << getBlockHandle() << std::endl; if (m_version > 12) { - (*m_ssBlock) << "330" << endl; - (*m_ssBlock) << m_saveBlkRecordHandle << endl; - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbEntity" << endl; - } - (*m_ssBlock) << " 8" << endl; - (*m_ssBlock) << "0" << endl; -// (*m_ssBlock) << " 62" << endl; -// (*m_ssBlock) << " 0" << endl; + m_ssBlock << "330" << std::endl; + m_ssBlock << m_saveBlkRecordHandle << std::endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbEntity" << std::endl; + } + m_ssBlock << " 8" << std::endl; + m_ssBlock << "0" << std::endl; +// m_ssBlock << " 62" << std::endl; +// m_ssBlock << " 0" << std::endl; if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbCircle" << endl; - } - (*m_ssBlock) << " 10" << endl; - (*m_ssBlock) << startExt2[0] << endl; //arc center - (*m_ssBlock) << " 20" << endl; - (*m_ssBlock) << startExt2[1] << endl; - (*m_ssBlock) << " 30" << endl; - (*m_ssBlock) << startExt2[2] << endl; - (*m_ssBlock) << " 40" << endl; - (*m_ssBlock) << radius << endl; //radius + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbCircle" << std::endl; + } + m_ssBlock << " 10" << std::endl; + m_ssBlock << startExt2[0] << std::endl; //arc center + m_ssBlock << " 20" << std::endl; + m_ssBlock << startExt2[1] << std::endl; + m_ssBlock << " 30" << std::endl; + m_ssBlock << startExt2[2] << std::endl; + m_ssBlock << " 40" << std::endl; + m_ssBlock << radius << std::endl; //radius if (m_version > 12) { - (*m_ssBlock) << "100" << endl; - (*m_ssBlock) << "AcDbArc" << endl; + m_ssBlock << "100" << std::endl; + m_ssBlock << "AcDbArc" << std::endl; } - (*m_ssBlock) << " 50" << endl; - (*m_ssBlock) << startAngle << endl; //start angle - (*m_ssBlock) << " 51" << endl; - (*m_ssBlock) << endAngle << endl; //end angle + m_ssBlock << " 50" << std::endl; + m_ssBlock << startAngle << std::endl; //start angle + m_ssBlock << " 51" << std::endl; + m_ssBlock << endAngle << std::endl; //end angle putText(dimText,toVector3d(textMidPoint), toVector3d(textMidPoint),3.5,1, m_ssBlock,getBlockHandle(),m_saveBlkRecordHandle); @@ -1704,14 +1674,14 @@ void CDxfWrite::writeBlocksSection(void) std::stringstream ss; ss << "blocks1" << m_version << ".rub"; std::string fileSpec = m_dataDir + ss.str(); - (*m_ofs) << getPlateFile(fileSpec); + m_ofs << getPlateFile(fileSpec); } //write blocks content - (*m_ofs) << (*m_ssBlock).str(); + m_ofs << m_ssBlock.str(); - (*m_ofs) << " 0" << endl; - (*m_ofs) << "ENDSEC" << endl; + m_ofs << " 0" << std::endl; + m_ofs << "ENDSEC" << std::endl; } //*************************** @@ -1722,14 +1692,14 @@ void CDxfWrite::writeEntitiesSection(void) std::stringstream ss; ss << "entities" << m_version << ".rub"; std::string fileSpec = m_dataDir + ss.str(); - (*m_ofs) << getPlateFile(fileSpec); + m_ofs << getPlateFile(fileSpec); //write entities content - (*m_ofs) << (*m_ssEntity).str(); + m_ofs << m_ssEntity.str(); - (*m_ofs) << " 0" << endl; - (*m_ofs) << "ENDSEC" << endl; + m_ofs << " 0" << std::endl; + m_ofs << "ENDSEC" << std::endl; } //*************************** @@ -1743,10 +1713,11 @@ void CDxfWrite::writeObjectsSection(void) std::stringstream ss; ss << "objects" << m_version << ".rub"; std::string fileSpec = m_dataDir + ss.str(); - (*m_ofs) << getPlateFile(fileSpec); + m_ofs << getPlateFile(fileSpec); } CDxfRead::CDxfRead(const char* filepath) + : m_ifs(filepath) { // start the file memset( m_str, '\0', sizeof(m_str) ); @@ -1760,18 +1731,14 @@ CDxfRead::CDxfRead(const char* filepath) memset( m_block_name, '\0', sizeof(m_block_name) ); m_ignore_errors = true; - m_ifs = new ifstream(filepath); - if(!(*m_ifs)){ + if (!m_ifs) m_fail = true; - return; - } - m_ifs->imbue(std::locale("C")); - + else + m_ifs.imbue(std::locale::classic()); } CDxfRead::~CDxfRead() { - delete m_ifs; } double CDxfRead::mm( double value ) const @@ -1815,7 +1782,7 @@ bool CDxfRead::ReadLine() double e[3] = {0, 0, 0}; bool hidden = false; - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -1913,7 +1880,7 @@ bool CDxfRead::ReadPoint() { double s[3] = {0, 0, 0}; - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -1997,7 +1964,7 @@ bool CDxfRead::ReadArc() double z_extrusion_dir = 1.0; bool hidden = false; - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -2103,7 +2070,7 @@ bool CDxfRead::ReadSpline() double temp_double; - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -2277,7 +2244,7 @@ bool CDxfRead::ReadCircle() double c[3] = {0,0,0}; // centre bool hidden = false; - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -2361,7 +2328,7 @@ bool CDxfRead::ReadText() memset( c, 0, sizeof(c) ); - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -2448,7 +2415,7 @@ bool CDxfRead::ReadEllipse() double start=0; //start of arc double end=0; // end of arc - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -2618,7 +2585,7 @@ bool CDxfRead::ReadLwPolyLine() int flags; bool next_item_found = false; - while(!((*m_ifs).eof()) && !next_item_found) + while(!m_ifs.eof() && !next_item_found) { get_line(); int n; @@ -2725,7 +2692,7 @@ bool CDxfRead::ReadVertex(double *pVertex, bool *bulge_found, double *bulge) pVertex[1] = 0.0; pVertex[2] = 0.0; - while(!(*m_ifs).eof()) { + while(!m_ifs.eof()) { get_line(); int n; if(sscanf(m_str, "%d", &n) != 1) { @@ -2798,7 +2765,7 @@ bool CDxfRead::ReadPolyLine() bool bulge_found; double bulge; - while(!(*m_ifs).eof()) + while(!m_ifs.eof()) { get_line(); int n; @@ -2920,7 +2887,7 @@ bool CDxfRead::ReadInsert() double rot = 0.0; // rotation char name[1024] = {0}; - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -3012,7 +2979,7 @@ bool CDxfRead::ReadDimension() double p[3] = {0,0,0}; // dimpoint double rot = -1.0; // rotation - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -3109,7 +3076,7 @@ bool CDxfRead::ReadDimension() bool CDxfRead::ReadBlockInfo() { - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -3150,7 +3117,7 @@ void CDxfRead::get_line() return; } - m_ifs->getline(m_str, 1024); + m_ifs.getline(m_str, 1024); ++m_lineNum; char str[1024]; @@ -3210,7 +3177,7 @@ bool CDxfRead::ReadLayer() std::string layername; int aci = -1; - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { get_line(); int n; @@ -3267,7 +3234,7 @@ void CDxfRead::DoRead(const bool ignore_errors /* = false */ ) get_line(); - while(!((*m_ifs).eof())) + while(!m_ifs.eof()) { m_aci = 256; @@ -3453,9 +3420,9 @@ void CDxfRead::ReportError_readInteger(const char* context) this->ReportError(msg.c_str()); } -streamsize CDxfRead::gcount() const +std::streamsize CDxfRead::gcount() const { - return m_ifs ? m_ifs->gcount() : 0; + return m_ifs.gcount(); } std::string CDxfRead::LayerName() const diff --git a/src/io_dxf/dxf.h b/src/io_dxf/dxf.h index 76f50317..9cd8b05e 100644 --- a/src/io_dxf/dxf.h +++ b/src/io_dxf/dxf.h @@ -5,9 +5,6 @@ // MAYO: file initially taken from FreeCad/src/Mod/Import/App/dxf.h -- commit #47d5707 -#ifndef _dxf_h_ -#define _dxf_h_ - #pragma once #include @@ -17,13 +14,12 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include #include "freecad.h" -#define ImportExport //Following is required to be defined on Ubuntu with OCC 6.3.1 #ifndef HAVE_IOSTREAM @@ -124,25 +120,25 @@ struct LWPolyDataOut }; //******************** -class ImportExport CDxfWrite{ +class CDxfWrite{ private: - std::ofstream* m_ofs; + std::ofstream m_ofs; bool m_fail; - std::ostringstream* m_ssBlock; - std::ostringstream* m_ssBlkRecord; - std::ostringstream* m_ssEntity; - std::ostringstream* m_ssLayer; + std::ostringstream m_ssBlock; + std::ostringstream m_ssBlkRecord; + std::ostringstream m_ssEntity; + std::ostringstream m_ssLayer; protected: void putLine(const Base::Vector3d& s, const Base::Vector3d& e, - std::ostringstream* outStream, const std::string& handle, + std::ostringstream& outStream, const std::string& handle, const std::string& ownerHandle); void putText(const char* text, const Base::Vector3d& location1, const Base::Vector3d& location2, const double height, const int horizJust, - std::ostringstream* outStream, const std::string& handle, + std::ostringstream& outStream, const std::string& handle, const std::string& ownerHandle); void putArrow(const Base::Vector3d& arrowPos, const Base::Vector3d& barb1Pos, const Base::Vector3d& barb2Pos, - std::ostringstream* outStream, const std::string& handle, + std::ostringstream& outStream, const std::string& handle, const std::string& ownerHandle); //! copy boiler plate file @@ -157,10 +153,6 @@ class ImportExport CDxfWrite{ std::string m_optionSource; int m_version; int m_handle; - int m_entityHandle; - int m_layerHandle; - int m_blockHandle; - int m_blkRecordHandle; bool m_polyOverride; std::string m_saveModelSpaceHandle; @@ -245,9 +237,9 @@ class ImportExport CDxfWrite{ }; // derive a class from this and implement it's virtual functions -class ImportExport CDxfRead{ +class CDxfRead{ private: - std::ifstream* m_ifs; + std::ifstream m_ifs; bool m_fail; char m_str[1024]; @@ -320,4 +312,3 @@ class ImportExport CDxfRead{ std::string LayerName() const; }; -#endif diff --git a/src/io_dxf/io_dxf.cpp b/src/io_dxf/io_dxf.cpp index 8f40e6b3..5d97b9ef 100644 --- a/src/io_dxf/io_dxf.cpp +++ b/src/io_dxf/io_dxf.cpp @@ -8,6 +8,7 @@ #include "../base/cpp_utils.h" #include "../base/document.h" +#include "../base/filepath.h" #include "../base/math_utils.h" #include "../base/messenger.h" #include "../base/property_builtins.h" @@ -114,7 +115,7 @@ class DxfReader::Properties : public PropertyGroup { this->importAnnotations.setDescription( textIdTr("Import text/dimension objects")); this->groupLayers.setDescription( - textIdTr("Group all objects within a layer into a single coumpound shape")); + textIdTr("Group all objects within a layer into a single compound shape")); this->fontNameForTextObjects.setDescription( textIdTr("Name of the font to be used when creating shape for text objects")); } @@ -190,7 +191,7 @@ TDF_LabelSequence DxfReader::transfer(DocumentPtr doc, TaskProgress* progress) } } auto fnUpdateProgressValue = [&]{ - progress->setValue(MathUtils::mappedValue(iShape, 0, shapeCount, 0, 100)); + progress->setValue(MathUtils::toPercent(iShape, 0, shapeCount)); }; if (!m_params.groupLayers) { @@ -269,34 +270,12 @@ void DxfReader::applyProperties(const PropertyGroup* group) } } -Span DxfFactoryReader::formats() const -{ - static const Format arrayFormat[] = { Format_DXF }; - return arrayFormat; -} - -std::unique_ptr DxfFactoryReader::create(Format format) const -{ - if (format == Format_DXF) - return std::make_unique(); - - return {}; -} - -std::unique_ptr DxfFactoryReader::createProperties(Format format, PropertyGroup* parentGroup) const -{ - if (format == Format_DXF) - return DxfReader::createProperties(parentGroup); - - return {}; -} - void DxfReader::Internal::get_line() { CDxfRead::get_line(); m_fileReadSize += this->gcount(); if (m_progress) - m_progress->setValue(MathUtils::mappedValue(m_fileReadSize, 0, m_fileSize, 0, 100)); + m_progress->setValue(MathUtils::toPercent(m_fileReadSize, 0, m_fileSize)); } DxfReader::Internal::Internal(const FilePath& filepath, TaskProgress* progress) @@ -304,7 +283,7 @@ DxfReader::Internal::Internal(const FilePath& filepath, TaskProgress* progress) m_progress(progress) { if (!this->Failed()) - m_fileSize = std::filesystem::file_size(filepath); + m_fileSize = filepathFileSize(filepath); } void DxfReader::Internal::OnReadLine(const double* s, const double* e, bool /*hidden*/) diff --git a/src/io_dxf/io_dxf.h b/src/io_dxf/io_dxf.h index 2c40aa30..840dd3c4 100644 --- a/src/io_dxf/io_dxf.h +++ b/src/io_dxf/io_dxf.h @@ -7,6 +7,8 @@ #pragma once #include "../base/io_reader.h" +#include "../base/io_single_format_factory.h" + #include #include #include @@ -45,12 +47,8 @@ class DxfReader : public Reader { Parameters m_params; }; -class DxfFactoryReader : public FactoryReader { -public: - Span formats() const override; - std::unique_ptr create(Format format) const override; - std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; -}; +// Provides factory to create DxfReader objects +class DxfFactoryReader : public SingleFormatFactoryReader {}; } // namespace IO } // namespace Mayo diff --git a/src/io_gmio/io_gmio_amf_writer.cpp b/src/io_gmio/io_gmio_amf_writer.cpp index d2826241..4dc5faca 100644 --- a/src/io_gmio/io_gmio_amf_writer.cpp +++ b/src/io_gmio/io_gmio_amf_writer.cpp @@ -10,8 +10,9 @@ #include "../base/brep_utils.h" #include "../base/caf_utils.h" #include "../base/cpp_utils.h" -#include "../base/data_triangulation.h" +#include "../base/triangulation_annex_data.h" #include "../base/math_utils.h" +#include "../base/mesh_access.h" #include "../base/meta_enum.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" @@ -30,6 +31,7 @@ #include #include +#include #include namespace Mayo { @@ -48,7 +50,7 @@ void gmio_handleProgress(void* cookie, intmax_t value, intmax_t maxValue) auto progress = static_cast(cookie); if (progress && maxValue > 0) { const auto pctNorm = value / double(maxValue); - const auto pct = qRound(pctNorm * 100); + const auto pct = std::round(pctNorm * 100); progress->setValue(pct); } } @@ -129,7 +131,7 @@ class GmioAmfWriter::Properties : public PropertyGroup { { this->float64Format.mutableEnumeration().changeTrContext(this->textIdContext()); this->float64Format.setDescription( - textIdTr("Format used when writting `double` values as strings")); + textIdTr("Format used when writing `double` values as strings")); this->float64Format.setDescriptions({ { FloatTextFormat::Decimal, textIdTr("Decimal floating point(ex: 392.65)") }, { FloatTextFormat::Scientific, textIdTr("Scientific notation(ex: 3.9265E+2)") }, @@ -139,7 +141,7 @@ class GmioAmfWriter::Properties : public PropertyGroup { this->float64Precision.setConstraintsEnabled(true); this->float64Precision.setRange(1, 16); this->float64Precision.setDescription( - textIdTr("Maximum number of significant digits when writting `double` values")); + textIdTr("Maximum number of significant digits when writing `double` values")); this->createZipArchive.setDescription( textIdTr("Write AMF document in ZIP archive containing one file entry")); @@ -239,8 +241,8 @@ bool GmioAmfWriter::transfer(Span spanAppItem, TaskProgre }; for (const ApplicationItem& appItem : spanAppItem) { - const int appItemIndex = &appItem - &spanAppItem.front(); - progress->setValue(MathUtils::mappedValue(appItemIndex, 0, spanAppItem.size() - 1, 0, 100)); + const auto appItemIndex = &appItem - &spanAppItem.front(); + progress->setValue(MathUtils::toPercent(appItemIndex, 0, spanAppItem.size() - 1)); const Tree& modelTree = appItem.document()->modelTree(); if (appItem.isDocument()) { traverseTree(modelTree, [&](TreeNodeId id) { fnCreateObject(modelTree, id); }); @@ -332,17 +334,11 @@ int GmioAmfWriter::createObject(const TDF_Label& labelShape) if (!shape.IsNull()) { BRepUtils::forEachSubFace(shape, [=](const TopoDS_Face& face){ TopLoc_Location loc; - const Handle_Poly_Triangulation& polyTri = BRep_Tool::Triangulation(face, loc); - fnAddMesh(polyTri, loc); + const auto& mesh = BRep_Tool::Triangulation(face, loc); + fnAddMesh(mesh, loc); }); } - // -- Triangulation ? - auto attrPolyTri = CafUtils::findAttribute(labelShape); - if (!attrPolyTri.IsNull()) { - fnAddMesh(attrPolyTri->Get(), TopLoc_Location()); - } - if (m_vecMesh.size() == meshCount) return -1; @@ -351,8 +347,7 @@ int GmioAmfWriter::createObject(const TDF_Label& labelShape) DocumentPtr doc = Document::findFrom(labelShape); if (doc && doc->xcaf().hasShapeColor(labelShape)) { const Quantity_Color color = doc->xcaf().shapeColor(labelShape); - auto itColor = std::find_if( - m_vecMaterial.cbegin(), m_vecMaterial.cend(), [=](const Material& mat) { + auto itColor = std::find_if(m_vecMaterial.cbegin(), m_vecMaterial.cend(), [=](const Material& mat) { return mat.color == color; }); if (itColor != m_vecMaterial.cend()) { diff --git a/src/io_image/io_image.cpp b/src/io_image/io_image.cpp index 552bd6a3..e1e071d2 100644 --- a/src/io_image/io_image.cpp +++ b/src/io_image/io_image.cpp @@ -126,10 +126,10 @@ bool ImageWriter::writeFile(const FilePath& filepath, TaskProgress* progress) } const auto itemProgress = &appItem - &m_vecAppItem.front(); - progress->setValue(MathUtils::mappedValue(itemProgress, 0, itemCount, 0, 100)); + progress->setValue(MathUtils::toPercent(itemProgress, 0, itemCount)); } - gfxScene.redraw(); + view->Redraw(); GraphicsUtils::V3dView_fitAll(view); Handle_Image_AlienPixMap pixmap = ImageWriter::createImage(view); if (!pixmap) diff --git a/src/io_occ/io_occ.cpp b/src/io_occ/io_occ.cpp index 269c7ddd..e2a732b5 100644 --- a/src/io_occ/io_occ.cpp +++ b/src/io_occ/io_occ.cpp @@ -12,7 +12,7 @@ #include "io_occ_iges.h" #include "io_occ_step.h" #include "io_occ_stl.h" -#include "io_occ_vrml.h" +#include "io_occ_vrml_writer.h" #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) # include "io_occ_gltf_reader.h" @@ -27,6 +27,10 @@ # include "io_occ_obj_writer.h" #endif +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 7, 0) +# include "io_occ_vrml_reader.h" +#endif + namespace Mayo { namespace IO { @@ -39,6 +43,9 @@ Span OccFactoryReader::formats() const #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 4, 0) , Format_GLTF, Format_OBJ #endif + #if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 7, 0) + , Format_VRML + #endif }; return arrayFormat; } @@ -61,6 +68,11 @@ std::unique_ptr OccFactoryReader::create(Format format) const return std::make_unique(); #endif +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 7, 0) + if (format == Format_VRML) + return std::make_unique(); +#endif + return {}; } @@ -78,6 +90,11 @@ PtrPropertyGroup OccFactoryReader::createProperties(Format format, PropertyGroup return OccObjReader::createProperties(parentGroup); #endif +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 7, 0) + if (format == Format_VRML) + return OccVrmlReader::createProperties(parentGroup); +#endif + return {}; } diff --git a/src/io_occ/io_occ_caf.cpp b/src/io_occ/io_occ_caf.cpp index b4df812c..bd522d4c 100644 --- a/src/io_occ/io_occ_caf.cpp +++ b/src/io_occ/io_occ_caf.cpp @@ -23,16 +23,16 @@ namespace IO { namespace { -template -bool cafGenericReadFile(CAF_READER& reader, const FilePath& filepath, TaskProgress* /*progress*/) +template +bool cafGenericReadFile(CafReaderType& reader, const FilePath& filepath, TaskProgress* /*progress*/) { //readFile_prepare(reader); const IFSelect_ReturnStatus error = reader.ReadFile(filepath.u8string().c_str()); return error == IFSelect_RetDone; } -template -TDF_LabelSequence cafGenericReadTransfer(CAF_READER& reader, DocumentPtr doc, TaskProgress* progress) +template +TDF_LabelSequence cafGenericReadTransfer(CafReaderType& reader, DocumentPtr doc, TaskProgress* progress) { Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); const TDF_LabelSequence seqMark = doc->xcaf().topLevelFreeShapes(); @@ -49,8 +49,8 @@ TDF_LabelSequence cafGenericReadTransfer(CAF_READER& reader, DocumentPtr doc, Ta return doc->xcaf().diffTopLevelFreeShapes(seqMark); } -template -bool cafGenericWriteTransfer(CAF_WRITER& writer, Span appItems, TaskProgress* progress) +template +bool cafGenericWriteTransfer(CafWriterType& writer, Span appItems, TaskProgress* progress) { Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); #if OCC_VERSION_HEX < OCC_VERSION_CHECK(7, 5, 0) diff --git a/src/io_occ/io_occ_caf.h b/src/io_occ/io_occ_caf.h index 696f5303..2391de69 100644 --- a/src/io_occ/io_occ_caf.h +++ b/src/io_occ/io_occ_caf.h @@ -34,8 +34,7 @@ namespace Private { std::mutex& cafGlobalMutex(); #define MayoIO_CafGlobalScopedLock(name) \ - std::lock_guard name(Mayo::IO::Private::cafGlobalMutex()); \ - Q_UNUSED(name); + [[maybe_unused]] std::lock_guard name(Mayo::IO::Private::cafGlobalMutex()); Handle_XSControl_WorkSession cafWorkSession(const IGESCAFControl_Reader& reader); Handle_XSControl_WorkSession cafWorkSession(const STEPCAFControl_Reader& reader); diff --git a/src/io_occ/io_occ_common.cpp b/src/io_occ/io_occ_common.cpp index f72c0d96..da935205 100644 --- a/src/io_occ/io_occ_common.cpp +++ b/src/io_occ/io_occ_common.cpp @@ -5,8 +5,12 @@ ****************************************************************************/ #include "io_occ_common.h" +#include "../base/meta_enum.h" #include "../base/text_id.h" +#include +#include + namespace Mayo { namespace IO { @@ -23,7 +27,7 @@ const char* OccCommon::toCafString(OccCommon::LengthUnit unit) case LengthUnit::Foot: return "FT"; case LengthUnit::Mile: return "MI"; } - Q_UNREACHABLE(); + throw std::invalid_argument(fmt::format("{} isn't supported", MetaEnum::name(unit))); } } // namespace IO diff --git a/src/io_occ/io_occ_gltf_writer.cpp b/src/io_occ/io_occ_gltf_writer.cpp index d2c0c379..08aa4b47 100644 --- a/src/io_occ/io_occ_gltf_writer.cpp +++ b/src/io_occ/io_occ_gltf_writer.cpp @@ -27,8 +27,10 @@ class OccGltfWriter::Properties : public PropertyGroup { Properties(PropertyGroup* parentGroup) : PropertyGroup(parentGroup) { - this->coordinatesConverter.setDescription( - textIdTr("Coordinate system transformation from OpenCascade to glTF")); + this->inputCoordinateSystem.setDescription( + textIdTr("Source coordinate system transformation")); + this->outputCoordinateSystem.setDescription( + textIdTr("Target coordinate system transformation")); this->transformationFormat.setDescription( textIdTr("Preferred transformation format for writing into glTF file")); this->forceExportUV.setDescription( @@ -50,22 +52,27 @@ class OccGltfWriter::Properties : public PropertyGroup { "If set to `false` then texture images will be written as separate files.\n\n" "Applicable only if option `{0}` is set to `{1}`"), this->format.label(), - MetaEnum::name(Format::Binary)) + MetaEnum::name(Format::Binary) + ) ); this->mergeFaces.setDescription( textIdTr("Merge faces within a single part.\n\n" - "May reduce JSON size thanks to smaller number of primitive arrays")); + "May reduce JSON size thanks to smaller number of primitive arrays") + ); this->keepIndices16b.setDescription( fmt::format(textIdTr("Prefer keeping 16-bit indexes while merging face.\n\n" "May reduce binary data size thanks to smaller triangle indexes.\n\n" "Applicable only if option `{}` is on"), - this->mergeFaces.label()) + this->mergeFaces.label() + ) ); } - void restoreDefaults() override { + void restoreDefaults() override + { const Parameters defaults; - this->coordinatesConverter.setValue(defaults.coordinatesConverter); + this->inputCoordinateSystem.setValue(defaults.inputCoordinateSystem); + this->outputCoordinateSystem.setValue(defaults.outputCoordinateSystem); this->transformationFormat.setValue(defaults.transformationFormat); this->format.setValue(defaults.format); this->forceExportUV.setValue(defaults.forceExportUV); @@ -94,7 +101,8 @@ class OccGltfWriter::Properties : public PropertyGroup { PropertyGroup::onPropertyChanged(prop); } - PropertyEnum coordinatesConverter{ this, textId("coordinatesConverter") }; + PropertyEnum inputCoordinateSystem{ this, textId("inputCoordinateSystem") }; + PropertyEnum outputCoordinateSystem{ this, textId("outputCoordinateSystem") }; PropertyEnum transformationFormat{ this, textId("transformationFormat") }; PropertyEnum format{ this, textId("format") }; PropertyBool forceExportUV{ this, textId("forceExportUV") }; @@ -136,6 +144,8 @@ bool OccGltfWriter::writeFile(const FilePath& filepath, TaskProgress* progress) Handle_Message_ProgressIndicator occProgress = new OccProgressIndicator(progress); const bool isBinary = m_params.format == Format::Binary; RWGltf_CafWriter writer(filepath.u8string().c_str(), isBinary); + writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(m_params.inputCoordinateSystem); + writer.ChangeCoordinateSystemConverter().SetOutputCoordinateSystem(m_params.outputCoordinateSystem); #if OCC_VERSION_HEX >= 0x070600 auto fnToOccNameFormat = [](ShapeNameFormat format) { switch (format) { @@ -193,7 +203,8 @@ void OccGltfWriter::applyProperties(const PropertyGroup* params) { auto ptr = dynamic_cast(params); if (ptr) { - m_params.coordinatesConverter = ptr->coordinatesConverter; + m_params.inputCoordinateSystem = ptr->inputCoordinateSystem; + m_params.outputCoordinateSystem = ptr->outputCoordinateSystem; m_params.forceExportUV = ptr->forceExportUV; m_params.format = ptr->format; m_params.transformationFormat = ptr->transformationFormat; diff --git a/src/io_occ/io_occ_gltf_writer.h b/src/io_occ/io_occ_gltf_writer.h index 7e88b939..1664f1b8 100644 --- a/src/io_occ/io_occ_gltf_writer.h +++ b/src/io_occ/io_occ_gltf_writer.h @@ -39,7 +39,8 @@ class OccGltfWriter : public Writer { }; struct Parameters { - RWMesh_CoordinateSystem coordinatesConverter = RWMesh_CoordinateSystem_glTF; + RWMesh_CoordinateSystem inputCoordinateSystem = RWMesh_CoordinateSystem_Undefined; + RWMesh_CoordinateSystem outputCoordinateSystem = RWMesh_CoordinateSystem_glTF; RWGltf_WriterTrsfFormat transformationFormat = RWGltf_WriterTrsfFormat_Compact; Format format = Format::Binary; bool forceExportUV = false; diff --git a/src/io_occ/io_occ_obj_writer.cpp b/src/io_occ/io_occ_obj_writer.cpp index 25641317..5ffdd300 100644 --- a/src/io_occ/io_occ_obj_writer.cpp +++ b/src/io_occ/io_occ_obj_writer.cpp @@ -25,16 +25,18 @@ class OccObjWriter::Properties : public PropertyGroup { Properties(PropertyGroup* parentGroup) : PropertyGroup(parentGroup) { - this->coordinatesConverter.setDescription( - textIdTr("Coordinate system transformation from OpenCascade to OBJ")); + this->inputCoordinateSystem.setDescription(textIdTr("Source coordinate system transformation")); + this->outputCoordinateSystem.setDescription(textIdTr("Target coordinate system transformation")); } void restoreDefaults() override { const Parameters defaults; - this->coordinatesConverter.setValue(defaults.coordinatesConverter); + this->inputCoordinateSystem.setValue(defaults.inputCoordinateSystem); + this->outputCoordinateSystem.setValue(defaults.outputCoordinateSystem); } - PropertyEnum coordinatesConverter{ this, textId("coordinatesConverter") }; + PropertyEnum inputCoordinateSystem{ this, textId("inputCoordinateSystem") }; + PropertyEnum outputCoordinateSystem{ this, textId("outputCoordinateSystem") }; }; bool OccObjWriter::transfer(Span spanAppItem, TaskProgress*) @@ -67,6 +69,8 @@ bool OccObjWriter::writeFile(const FilePath& filepath, TaskProgress* progress) Handle_Message_ProgressIndicator occProgress = new OccProgressIndicator(progress); RWObj_CafWriter writer(filepath.u8string().c_str()); + writer.ChangeCoordinateSystemConverter().SetInputCoordinateSystem(m_params.inputCoordinateSystem); + writer.ChangeCoordinateSystemConverter().SetOutputCoordinateSystem(m_params.outputCoordinateSystem); const TColStd_IndexedDataMapOfStringString fileInfo; if (m_seqRootLabel.IsEmpty()) return writer.Perform(m_document, fileInfo, occProgress->Start()); @@ -83,7 +87,8 @@ void OccObjWriter::applyProperties(const PropertyGroup* params) { auto ptr = dynamic_cast(params); if (ptr) { - m_params.coordinatesConverter = ptr->coordinatesConverter; + m_params.inputCoordinateSystem = ptr->inputCoordinateSystem; + m_params.outputCoordinateSystem = ptr->outputCoordinateSystem; } } diff --git a/src/io_occ/io_occ_obj_writer.h b/src/io_occ/io_occ_obj_writer.h index 575c28b6..31567e7b 100644 --- a/src/io_occ/io_occ_obj_writer.h +++ b/src/io_occ/io_occ_obj_writer.h @@ -28,7 +28,8 @@ class OccObjWriter : public Writer { // Parameters struct Parameters { - RWMesh_CoordinateSystem coordinatesConverter = RWMesh_CoordinateSystem_glTF; + RWMesh_CoordinateSystem inputCoordinateSystem = RWMesh_CoordinateSystem_Undefined; + RWMesh_CoordinateSystem outputCoordinateSystem = RWMesh_CoordinateSystem_glTF; }; Parameters& parameters() { return m_params; } const Parameters& constParameters() const { return m_params; } diff --git a/src/io_occ/io_occ_step.cpp b/src/io_occ/io_occ_step.cpp index 311239a8..0cc929cb 100644 --- a/src/io_occ/io_occ_step.cpp +++ b/src/io_occ/io_occ_step.cpp @@ -6,6 +6,7 @@ #include "io_occ_step.h" #include "io_occ_caf.h" +#include "../base/meta_enum.h" #include "../base/occ_static_variables_rollback.h" #include "../base/property_builtins.h" #include "../base/property_enumeration.h" @@ -18,6 +19,8 @@ #include #include #include +#include +#include namespace Mayo { namespace IO { @@ -199,7 +202,7 @@ void OccStepReader::changeStaticVariables(OccStaticVariablesRollback* rollback) case Encoding::ISO_8859_9: return "iso8859-9"; #endif } - Q_UNREACHABLE(); + throw std::invalid_argument(fmt::format("{} isn't supported", MetaEnum::name(code))); }; const char strKeyReadStepCodePage[] = diff --git a/src/io_occ/io_occ_stl.cpp b/src/io_occ/io_occ_stl.cpp index 62e0699e..c61697c9 100644 --- a/src/io_occ/io_occ_stl.cpp +++ b/src/io_occ/io_occ_stl.cpp @@ -9,9 +9,10 @@ #include "../base/application_item.h" #include "../base/brep_utils.h" #include "../base/caf_utils.h" -#include "../base/data_triangulation.h" +#include "../base/triangulation_annex_data.h" #include "../base/document.h" #include "../base/filepath_conv.h" +#include "../base/messenger.h" #include "../base/occ_progress_indicator.h" #include "../base/property_enumeration.h" #include "../base/task_progress.h" @@ -32,39 +33,42 @@ namespace { static TopoDS_Shape asShape(const DocumentPtr& doc) { TopoDS_Shape shape; - const TDF_LabelSequence seqFreeShape = doc->xcaf().topLevelFreeShapes(); - if (seqFreeShape.Size() > 1) { + + if (doc->entityCount() == 1) { + shape = XCaf::shape(doc->entityLabel(0)); + } + else if (doc->entityCount() > 1) { TopoDS_Compound cmpd; BRep_Builder builder; builder.MakeCompound(cmpd); - for (const TDF_Label& label : seqFreeShape) - builder.Add(cmpd, XCaf::shape(label)); + for (int i = 0; i < doc->entityCount(); ++i) + builder.Add(cmpd, XCaf::shape(doc->entityLabel(i))); shape = cmpd; } - else if (seqFreeShape.Size() == 1) { - shape = XCaf::shape(seqFreeShape.First()); - } return shape; } } // namespace +struct OccStlWriterI18N { + MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::IO::OccStlWriterI18N) +}; + class OccStlWriter::Properties : public PropertyGroup { - MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::IO::OccStlWriter::Properties) public: Properties(PropertyGroup* parentGroup) : PropertyGroup(parentGroup) { - this->targetFormat.mutableEnumeration().changeTrContext(this->textIdContext()); + this->targetFormat.mutableEnumeration().changeTrContext(OccStlWriterI18N::textIdContext()); } void restoreDefaults() override { this->targetFormat.setValue(Format::Binary); } - PropertyEnum targetFormat{ this, textId("targetFormat") }; + PropertyEnum targetFormat{ this, OccStlWriterI18N::textId("targetFormat") }; }; bool OccStlReader::readFile(const FilePath& filepath, TaskProgress* progress) @@ -80,8 +84,9 @@ TDF_LabelSequence OccStlReader::transfer(DocumentPtr doc, TaskProgress* /*progre if (m_mesh.IsNull()) return {}; - const TDF_Label entityLabel = doc->newEntityLabel(); - DataTriangulation::Set(entityLabel, m_mesh); + const TDF_Label entityLabel = doc->newEntityShapeLabel(); + doc->xcaf().setShape(entityLabel, BRepUtils::makeFace(m_mesh)); + TriangulationAnnexData::Set(entityLabel); // IMPORTANT: pure mesh part marker! TDataStd_Name::Set(entityLabel, filepathTo(m_baseFilename)); return CafUtils::makeLabelSequence({ entityLabel }); } @@ -92,7 +97,6 @@ bool OccStlWriter::transfer(Span appItems, TaskProgress* // return Result::error(tr("OpenCascade RWStl does not support multi-solids")); m_shape = {}; - m_mesh = {}; if (!appItems.empty()) { const ApplicationItem& item = appItems.front(); if (item.isDocument()) { @@ -100,18 +104,12 @@ bool OccStlWriter::transfer(Span appItems, TaskProgress* } else if (item.isDocumentTreeNode()) { const TDF_Label label = item.documentTreeNode().label(); - if (XCaf::isShape(label)) { + if (XCaf::isShape(label)) m_shape = XCaf::shape(label); - } - else { - auto attrPolyTri = CafUtils::findAttribute(label); - if (!attrPolyTri.IsNull()) - m_mesh = attrPolyTri->Get(); - } } } - return !m_shape.IsNull() || !m_mesh.IsNull(); + return !m_shape.IsNull(); } bool OccStlWriter::writeFile(const FilePath& filepath, TaskProgress* progress) @@ -120,31 +118,28 @@ bool OccStlWriter::writeFile(const FilePath& filepath, TaskProgress* progress) bool facesMeshed = true; BRepUtils::forEachSubFace(m_shape, [&](const TopoDS_Face& face) { TopLoc_Location loc; - Handle_Poly_Triangulation mesh = BRep_Tool::Triangulation(face, loc); + const auto& mesh = BRep_Tool::Triangulation(face, loc); if (mesh.IsNull()) facesMeshed = false; }); if (!facesMeshed) { #if OCC_VERSION_HEX <= OCC_VERSION_CHECK(7, 3, 0) - //qCritical() << "Not all BRep faces are meshed"; + this->messenger()->emitError(OccStlWriterI18N::textIdTr("Not all BRep faces are meshed")); return false; // Continuing would crash #else - //qWarning() << "Not all BRep faces are meshed"; + this->messenger()->emitWarning(OccStlWriterI18N::textIdTr("Not all BRep faces are meshed")); #endif } StlAPI_Writer writer; writer.ASCIIMode() = m_params.format == Format::Ascii; - return writer.Write(m_shape, filepath.u8string().c_str()); - } - else if (!m_mesh.IsNull()) { + const std::string strFilepath = filepath.u8string(); +#if OCC_VERSION_HEX >= OCC_VERSION_CHECK(7, 5, 0) Handle_Message_ProgressIndicator indicator = new OccProgressIndicator(progress); - const std::string filepathUtf8 = filepath.u8string(); - const OSD_Path osdFilepath(filepathUtf8.c_str()); - if (m_params.format == Format::Ascii) - return RWStl::WriteAscii(m_mesh, osdFilepath, TKernelUtils::start(indicator)); - else - return RWStl::WriteBinary(m_mesh, osdFilepath, TKernelUtils::start(indicator)); + return writer.Write(m_shape, strFilepath.c_str(), TKernelUtils::start(indicator)); +#else + return writer.Write(m_shape, strFilepath.c_str()); +#endif } return false; diff --git a/src/io_occ/io_occ_stl.h b/src/io_occ/io_occ_stl.h index a55674b8..90df8723 100644 --- a/src/io_occ/io_occ_stl.h +++ b/src/io_occ/io_occ_stl.h @@ -48,7 +48,6 @@ class OccStlWriter : public Writer { class Properties; Parameters m_params; TopoDS_Shape m_shape; - Handle_Poly_Triangulation m_mesh; }; } // namespace IO diff --git a/src/io_occ/io_occ_vrml_reader.cpp b/src/io_occ/io_occ_vrml_reader.cpp new file mode 100644 index 00000000..fc448e3d --- /dev/null +++ b/src/io_occ/io_occ_vrml_reader.cpp @@ -0,0 +1,23 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "io_occ_vrml_reader.h" + +namespace Mayo { +namespace IO { + +OccVrmlReader::OccVrmlReader() + : OccBaseMeshReader(m_reader) +{ +} + +std::unique_ptr OccVrmlReader::createProperties(PropertyGroup* parentGroup) +{ + return std::make_unique(parentGroup); +} + +} // namespace IO +} // namespace Mayo diff --git a/src/io_occ/io_occ_vrml_reader.h b/src/io_occ/io_occ_vrml_reader.h new file mode 100644 index 00000000..0f1d5e62 --- /dev/null +++ b/src/io_occ/io_occ_vrml_reader.h @@ -0,0 +1,32 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "io_occ_base_mesh.h" +#include + +namespace Mayo { +namespace IO { + +// OpenCascade-based reader for VRML file format +// Requires OpenCascade >= v7.7.0 +class OccVrmlReader : public OccBaseMeshReader { +public: + OccVrmlReader(); + + static std::unique_ptr createProperties(PropertyGroup* parentGroup); + + OccBaseMeshReader::Parameters& parameters() override { return m_params; } + const OccBaseMeshReader::Parameters& constParameters() const override { return m_params; } + +private: + VrmlAPI_CafReader m_reader; + OccBaseMeshReader::Parameters m_params; +}; + +} // namespace IO +} // namespace Mayo diff --git a/src/io_occ/io_occ_vrml.cpp b/src/io_occ/io_occ_vrml_writer.cpp similarity index 93% rename from src/io_occ/io_occ_vrml.cpp rename to src/io_occ/io_occ_vrml_writer.cpp index 153a426e..61ca2906 100644 --- a/src/io_occ/io_occ_vrml.cpp +++ b/src/io_occ/io_occ_vrml_writer.cpp @@ -4,7 +4,7 @@ ** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt ****************************************************************************/ -#include "io_occ_vrml.h" +#include "io_occ_vrml_writer.h" #include "../base/application_item.h" #include "../base/caf_utils.h" @@ -60,14 +60,15 @@ bool OccVrmlWriter::transfer(Span spanAppItem, TaskProgre converter.AddShape(XCaf::shape(label)); } - const int index = &appItem - &spanAppItem.front(); - progress->setValue(MathUtils::mappedValue(index, 0, spanAppItem.size() - 1, 0, 100)); + const auto index = &appItem - &spanAppItem.front(); + progress->setValue(MathUtils::toPercent(index, 0, spanAppItem.size() - 1)); } const auto rep = m_shapeRepresentation; converter.Convert( rep == VrmlAPI_ShadedRepresentation || rep == VrmlAPI_BothRepresentation, - rep == VrmlAPI_WireFrameRepresentation || rep == VrmlAPI_BothRepresentation); + rep == VrmlAPI_WireFrameRepresentation || rep == VrmlAPI_BothRepresentation + ); return true; } diff --git a/src/io_occ/io_occ_vrml.h b/src/io_occ/io_occ_vrml_writer.h similarity index 100% rename from src/io_occ/io_occ_vrml.h rename to src/io_occ/io_occ_vrml_writer.h diff --git a/src/io_off/io_off_reader.cpp b/src/io_off/io_off_reader.cpp new file mode 100644 index 00000000..2adffc09 --- /dev/null +++ b/src/io_off/io_off_reader.cpp @@ -0,0 +1,376 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "io_off_reader.h" + +#include "../base/brep_utils.h" +#include "../base/caf_utils.h" +#include "../base/cpp_utils.h" +#include "../base/triangulation_annex_data.h" +#include "../base/document.h" +#include "../base/filepath_conv.h" +#include "../base/math_utils.h" +#include "../base/mesh_utils.h" +#include "../base/messenger.h" +#include "../base/property_builtins.h" +#include "../base/span.h" +#include "../base/task_progress.h" +#include "../base/tkernel_utils.h" + +#include +#include +#include + +#if __cpp_lib_to_chars +# include +#else +# include +#endif +#include +#include +#include +#include +#include + +namespace Mayo { +namespace IO { + +struct OffReaderI18N { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::IO::OffReaderI18N) }; + +namespace { + +const char* strEnd(std::string_view str) +{ + return str.data() + str.size(); +} + +std::string_view getWord(const std::string& strLine, std::string::size_type pos = 0) +{ + using IntType = std::string::size_type; + auto fnIsSpace = [](char ch) { + return std::isspace(ch, std::locale::classic()); + }; + + IntType wordStart = pos; + for (; wordStart < strLine.size() && fnIsSpace(strLine.at(wordStart)); ++wordStart); + + IntType wordEnd = wordStart; + for (; wordEnd < strLine.size() && !fnIsSpace(strLine.at(wordEnd)) && strLine.at(wordEnd) != '#'; ++wordEnd); + + return std::string_view{ strLine.c_str() + wordStart, wordEnd - wordStart }; +} + +template +std::array getWords(const std::string& strLine, std::string::size_type pos = 0) +{ + std::array arrayWord; + for (unsigned i = 0; i < N; ++i) { + const std::string::size_type offset = i > 0 ? strEnd(arrayWord[i - 1]) - strLine.data() : pos; + std::string_view word = getWord(strLine, offset); + if (!word.empty()) + arrayWord[i] = word; + else + return arrayWord; + } + + return arrayWord; +} + +void getWords(const std::string& strLine, std::vector& vecOutWord, std::string::size_type pos = 0) +{ + vecOutWord.clear(); + while (true) { + const std::string::size_type offset = !vecOutWord.empty() ? strEnd(vecOutWord.back()) - strLine.data() : pos; + std::string_view word = getWord(strLine, offset); + if (!word.empty()) + vecOutWord.push_back(word); + else + return; + } +} + +bool hasEmptyString(Span spanStr) +{ + for (std::string_view str : spanStr) { + if (str.empty()) + return true; + } + + return false; +} + +bool isAnyOf(std::string_view str, std::initializer_list listCandidates) +{ + for (std::string_view candidate : listCandidates) { + if (str == candidate) + return true; + } + + return false; +} + +template +T strToNum(std::string_view str) +{ + static_assert(std::is_arithmetic_v>, "Input template type must be arithmetic type"); + T num = {}; + if (str.empty()) + return num; + +#if __cpp_lib_to_chars + auto result = std::from_chars(str.data(), str.data() + str.size(), num); + if (result.ec != std::errc()) { + // TODO Handle error code + // throw std::runtime_error(std::make_error_code(err).message()); + } +#else + errno = 0; + num = std::strtod(str.data(), nullptr); + if (errno != 0) { + // TODO Handle error code + // throw std::runtime_error(std::strerror(errno)); + } +#endif + return num; +} + +void getNonCommentLine(std::istream& istr, std::string& strLine) +{ + std::getline(istr >> std::ws, strLine); + while (!istr.eof() && !strLine.empty() && strLine.front() == '#') + std::getline(istr >> std::ws, strLine); +} + +std::uint32_t strToColorComponent(std::string_view str) +{ + const double v = strToNum(str); + return unsigned(v > 1. ? v : v * 255); +} + +std::uint32_t toRgbaColor(Span spanWord) +{ + const unsigned r = spanWord.size() > 0 ? strToColorComponent(spanWord[0]) : 0; + const unsigned g = spanWord.size() > 1 ? strToColorComponent(spanWord[1]) : 0; + const unsigned b = spanWord.size() > 2 ? strToColorComponent(spanWord[2]) : 0; + const unsigned a = spanWord.size() > 3 ? strToColorComponent(spanWord[3]) : 0; + const std::uint32_t color = + ((r << 24) & 0xFF000000) + | ((g << 16) & 0x00FF0000) + | ((b << 8) & 0x0000FF00) + | (a & 0x000000FF); + return color; +} + +} // namespace + +bool OffReader::readFile(const FilePath& filepath, TaskProgress* progress) +{ + auto fnError = [=](std::string_view strMessage) { + this->messenger()->emitError(strMessage); + return false; + }; + + // Reset internal data + m_baseFilename = filepath.stem(); + m_vecVertex.clear(); + m_vecAllFacetIndex.clear(); + m_vecFacet.clear(); + + std::ifstream ifs(filepath); + if (!ifs.is_open()) + return fnError(OffReaderI18N::textIdTr("Can't open input file")); + + std::string strLine; + + // Consume header keyword + { + getNonCommentLine(ifs, strLine); + if (!ifs.good()) + return fnError(OffReaderI18N::textIdTr("Unexpected end of file")); + + std::string_view headerKeyword = getWord(strLine); + if (!isAnyOf(headerKeyword, { "OFF", "COFF", "NOFF", "4OFF" })) + return fnError(OffReaderI18N::textIdTr("Wrong header keyword(should be [C][N][4]OFF")); + } + + // Consume count of vertices/faces/edges + int vertexCount = 0; + int facetCount = 0; + { + if (!ifs.good()) + return fnError(OffReaderI18N::textIdTr("Unexpected end of file")); + + getNonCommentLine(ifs, strLine); + const auto arrayStrCount = getWords<2>(strLine); + if (hasEmptyString(arrayStrCount)) + return fnError(OffReaderI18N::textIdTr("No vertex or face count")); + + vertexCount = strToNum(arrayStrCount[0]); + facetCount = strToNum(arrayStrCount[1]); + } + + // Helper function for progress report + auto fnUpdateProgress = [=]{ + const auto total = vertexCount + facetCount; + const auto current = m_vecVertex.size() + m_vecFacet.size(); + if (current % 100 || CppUtils::cmpGreaterEqual(current, total)) + progress->setValue(MathUtils::toPercent(current, 0, total)); + }; + + // Consume vertices + m_vecVertex.reserve(vertexCount); + while (!ifs.eof() && CppUtils::cmpLess(m_vecVertex.size(), vertexCount)) { + getNonCommentLine(ifs, strLine); + const auto arrayStrCoord = getWords<3>(strLine); + if (hasEmptyString(arrayStrCoord)) + return fnError(OffReaderI18N::textIdTr("No vertex coordinates at current line")); + + Vertex vertex = {}; + vertex.coords.SetX(strToNum(arrayStrCoord[0])); + vertex.coords.SetY(strToNum(arrayStrCoord[1])); + vertex.coords.SetZ(strToNum(arrayStrCoord[2])); + + const auto arrayStrColor = getWords<4>(strLine, strEnd(arrayStrCoord.back()) - strLine.data()); + vertex.hasColor = !arrayStrColor.front().empty(); + if (vertex.hasColor) + vertex.color = toRgbaColor(arrayStrColor); + + m_vecVertex.push_back(std::move(vertex)); + fnUpdateProgress(); + } + + // Consume faces + m_vecAllFacetIndex.reserve(facetCount * 3); + m_vecFacet.reserve(facetCount); + std::vector vecWord; + while (!ifs.eof() && CppUtils::cmpLess(m_vecFacet.size(), facetCount)) { + getNonCommentLine(ifs, strLine); + getWords(strLine, vecWord); + const Facet facet = { int(m_vecAllFacetIndex.size()), strToNum(vecWord.front()) }; + if (CppUtils::cmpLess((vecWord.size() + 1), facet.vertexCount)) + return fnError(OffReaderI18N::textIdTr("Inconsistent vertex count of face")); + + for (int i = 0; i < facet.vertexCount; ++i) { + const int facetVertexId = strToNum(vecWord.at(i + 1)); + m_vecAllFacetIndex.push_back(facetVertexId); + } + +#if 0 + const bool facetHasColor = (vecWord.size() - 1 - facet.vertexCount) >= 3; + std::uint32_t facetColor = 0; + if (facetHasColor) { + const std::size_t colorComponentCount = vecWord.size() - (facet.vertexCount + 1); + facetColor = toRgbaColor({ &vecWord.at(facet.vertexCount + 1), colorComponentCount }); + for (int i = 0; i < facet.vertexCount; ++i) { + const int ivertex = m_vecAllFacetIndex.at(facet.startIndexInArray + i); + Vertex& vertex = m_vecVertex.at(ivertex); + } + } +#endif + + m_vecFacet.push_back(std::move(facet)); + fnUpdateProgress(); + } + + return true; +} + +TDF_LabelSequence OffReader::transfer(DocumentPtr doc, TaskProgress* progress) +{ + if (m_vecVertex.empty()) + return {}; + + TDF_Label entityLabel; + if (!m_vecAllFacetIndex.empty()) + entityLabel = this->transferMesh(doc, progress); + else + entityLabel = this->transferPointCloud(doc, progress); + + if (!entityLabel.IsNull()) { + TDataStd_Name::Set(entityLabel, filepathTo(m_baseFilename)); + return CafUtils::makeLabelSequence({ entityLabel }); + } + + return {}; +} + +TDF_Label OffReader::transferMesh(DocumentPtr doc, TaskProgress* progress) +{ + // Vertex and triangle count + const int vertexCount = CppUtils::safeStaticCast(m_vecVertex.size()); + int triangleCount = 0; + for (const Facet& facet : m_vecFacet) + triangleCount += facet.vertexCount - 2; + + // Create mesh object + Handle_Poly_Triangulation mesh = new Poly_Triangulation(vertexCount, triangleCount, false/*!hasUvNodes*/); + + // Helper function for progress report + auto fnUpdateProgress = [=](int current) { + const auto total = vertexCount + triangleCount; + if (current % 100 || current >= total) + progress->setValue(MathUtils::toPercent(current, 0, total)); + }; + + // Transfer vertices and prepare vertex colors + std::vector vecVertexColor; + vecVertexColor.reserve(m_vecVertex.size()); + for (const Vertex& vertex : m_vecVertex) { + const auto ivertex = &vertex - &m_vecVertex.front(); + MeshUtils::setNode(mesh, ivertex + 1, vertex.coords); + const std::uint32_t c = vertex.color; + if (vertex.hasColor) { + vecVertexColor.push_back( + Quantity_Color{ + ((c & 0xFF000000) >> 24) / 255.f, + ((c & 0x00FF0000) >> 16) / 255.f, + ((c & 0x0000FF00) >> 8) / 255.f, + TKernelUtils::preferredRgbColorType() + }); + } + else { + vecVertexColor.push_back(Quantity_NOC_BEIGE); + } + + fnUpdateProgress(ivertex); + } + + // Transfer faces + int iTriangle = 0; + for (const Facet& facet: m_vecFacet) { + const int facet0 = m_vecAllFacetIndex.at(facet.startIndexInArray); + if (facet.vertexCount == 3) { + const int facet1 = m_vecAllFacetIndex.at(facet.startIndexInArray + 1); + const int facet2 = m_vecAllFacetIndex.at(facet.startIndexInArray + 2); + MeshUtils::setTriangle(mesh, iTriangle + 1, { facet0 + 1, facet1 + 1, facet2 + 1 }); + ++iTriangle; + } + else if (facet.vertexCount > 3) { + for (int i = 1; i <= facet.vertexCount - 2; ++i) { + const int facetN = m_vecAllFacetIndex.at(facet.startIndexInArray + i); + const int facetM = m_vecAllFacetIndex.at(facet.startIndexInArray + i + 1); + MeshUtils::setTriangle(mesh, iTriangle + 1, { facet0 + 1, facetN + 1, facetM + 1 }); + ++iTriangle; + } + } + + fnUpdateProgress(vertexCount + iTriangle); + } + + // Insert mesh as a document entity + const TDF_Label entityLabel = doc->newEntityShapeLabel(); + doc->xcaf().setShape(entityLabel, BRepUtils::makeFace(mesh)); // IMPORTANT: pure mesh part marker! + TriangulationAnnexData::Set(entityLabel, std::move(vecVertexColor)); + return entityLabel; +} + +TDF_Label OffReader::transferPointCloud(DocumentPtr /*doc*/, TaskProgress* /*progress*/) +{ + return {}; +} + +} // namespace IO +} // namespace Mayo diff --git a/src/io_off/io_off_reader.h b/src/io_off/io_off_reader.h new file mode 100644 index 00000000..eba56187 --- /dev/null +++ b/src/io_off/io_off_reader.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/io_reader.h" +#include "../base/io_single_format_factory.h" + +#include +#include + +#include + +namespace Mayo { +namespace IO { + +// Reader for OFF file format +class OffReader : public Reader { +public: + bool readFile(const FilePath& filepath, TaskProgress* progress) override; + TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) override; + void applyProperties(const PropertyGroup*) override {} + + static std::unique_ptr createProperties(PropertyGroup*) { return {}; } + +private: + TDF_Label transferMesh(DocumentPtr doc, TaskProgress* progress); + TDF_Label transferPointCloud(DocumentPtr doc, TaskProgress* progress); + + struct Vertex { + gp_Pnt coords; + std::uint32_t color; + bool hasColor = false; + }; + + struct Facet { + int startIndexInArray; + int vertexCount; + }; + + FilePath m_baseFilename; + std::vector m_vecVertex; + std::vector m_vecAllFacetIndex; + std::vector m_vecFacet; +}; + +// Provides factory to create OffReader objects +class OffFactoryReader : public SingleFormatFactoryReader {}; + +} // namespace IO +} // namespace Mayo diff --git a/src/io_off/io_off_writer.cpp b/src/io_off/io_off_writer.cpp new file mode 100644 index 00000000..19519e65 --- /dev/null +++ b/src/io_off/io_off_writer.cpp @@ -0,0 +1,121 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "io_off_writer.h" + +#include "../base/caf_utils.h" +#include "../base/document.h" +#include "../base/label_data.h" +#include "../base/io_system.h" +#include "../base/mesh_access.h" +#include "../base/messenger.h" +#include "../base/property_builtins.h" +#include "../base/task_progress.h" +#include "../base/text_id.h" + +#include + +#include +#include +#include + +namespace Mayo { +namespace IO { + +struct OffWriterI18N { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::IO::OffWriterI18N) }; + +bool OffWriter::transfer(Span appItems, TaskProgress* /*progress*/) +{ + m_vecTreeNode.clear(); + m_vecTreeNode.reserve(appItems.size()); + System::traverseUniqueItems(appItems, [&](const DocumentTreeNode& treeNode) { + if (treeNode.isLeaf()) + m_vecTreeNode.push_back(treeNode); + }); + return true; +} + +bool OffWriter::writeFile(const FilePath& filepath, TaskProgress* progress) +{ + progress = progress ? progress : &TaskProgress::null(); + std::ofstream fstr(filepath); + if (!fstr.is_open()) { + this->messenger()->emitError(OffWriterI18N::textIdTr("Failed to open file")); + return false; + } + + fstr.imbue(std::locale::classic()); + fstr << "OFF\n"; + + // Count vertices and facets + int vertexCount = 0; + int facetCount = 0; + for (const DocumentTreeNode& treeNode : m_vecTreeNode) { + IMeshAccess_visitMeshes(treeNode, [&](const IMeshAccess& mesh) { + vertexCount += mesh.triangulation()->NbNodes(); + facetCount += mesh.triangulation()->NbTriangles(); + }); + } + + // Helper function for progress report + auto fnUpdateProgress = [=](int current) { + const auto total = vertexCount + facetCount; + if (current % 100 || current >= total) + progress->setValue(MathUtils::toPercent(current, 0, total)); + }; + + fstr << vertexCount << " " << facetCount << " " << 0/*edgeCount*/ << "\n"; + // Write vertices + int ivertex = 0; + for (const DocumentTreeNode& treeNode : m_vecTreeNode) { + IMeshAccess_visitMeshes(treeNode, [&](const IMeshAccess& mesh) { + const gp_Trsf& meshTrsf = mesh.location().Transformation(); + const Handle(Poly_Triangulation)& triangulation = mesh.triangulation(); + for (int i = 1; i <= triangulation->NbNodes(); ++i) { + const gp_Pnt pnt = triangulation->Node(i).Transformed(meshTrsf); + const std::optional color = mesh.nodeColor(i - 1); + fstr << pnt.X() << " " << pnt.Y() << " " << pnt.Z(); + if (color.has_value()) { + //fstr << " " << int(color->Red() * 255) + // << " " << int(color->Green() * 255) + // << " " << int(color->Blue() * 255); + fstr << " " << color->Red() << " " << color->Green() << " " << color->Blue(); + } + + fstr << "\n"; + fnUpdateProgress(++ivertex); + } + }); + } + + // Write facets(triangles) + int offsetVertex = 0; + int ifacet = 0; + for (const DocumentTreeNode& treeNode : m_vecTreeNode) { + IMeshAccess_visitMeshes(treeNode, [&](const IMeshAccess& mesh) { + const Handle(Poly_Triangulation)& triangulation = mesh.triangulation(); + for (int i = 1; i <= triangulation->NbTriangles(); ++i) { + const Poly_Triangle& tri = triangulation->Triangle(i); + fstr << "3 " + << offsetVertex + tri.Value(1) - 1 << " " + << offsetVertex + tri.Value(2) - 1 << " " + << offsetVertex + tri.Value(3) - 1 << "\n"; + fnUpdateProgress(vertexCount + (++ifacet)); + } + + offsetVertex += triangulation->NbNodes(); + }); + } + + return true; +} + +void OffWriter::applyProperties(const PropertyGroup*) +{ +} + +} // namespace IO +} // namespace Mayo diff --git a/src/io_off/io_off_writer.h b/src/io_off/io_off_writer.h new file mode 100644 index 00000000..a62f526a --- /dev/null +++ b/src/io_off/io_off_writer.h @@ -0,0 +1,35 @@ +/**************************************************************************** +** Copyright (c) 2023, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#pragma once + +#include "../base/document_tree_node.h" +#include "../base/io_writer.h" +#include "../base/io_single_format_factory.h" + +#include + +namespace Mayo { +namespace IO { + +// Writer for OFF file format +class OffWriter : public Writer { +public: + bool transfer(Span appItems, TaskProgress* progress) override; + bool writeFile(const FilePath& filepath, TaskProgress* progress) override; + void applyProperties(const PropertyGroup* group) override; + + static std::unique_ptr createProperties(PropertyGroup*) { return {}; } + +private: + std::vector m_vecTreeNode; +}; + +// Provides factory to create OffWriter objects +class OffFactoryWriter : public SingleFormatFactoryWriter {}; + +} // namespace IO +} // namespace Mayo diff --git a/src/io_ply/commit_miniply.txt b/src/io_ply/commit_miniply.txt index 47f6a3d7..32f52e26 100644 --- a/src/io_ply/commit_miniply.txt +++ b/src/io_ply/commit_miniply.txt @@ -1 +1 @@ -d7005b901e64c66bfb39e88c4882d006dcf70628 +1a235c70390fadf789695c9ccbf285ae712416b3 diff --git a/src/io_ply/io_ply_reader.cpp b/src/io_ply/io_ply_reader.cpp index d1375d00..50798dbe 100644 --- a/src/io_ply/io_ply_reader.cpp +++ b/src/io_ply/io_ply_reader.cpp @@ -5,13 +5,16 @@ ****************************************************************************/ #include "io_ply_reader.h" + +#include "../base/brep_utils.h" #include "../base/caf_utils.h" #include "../base/cpp_utils.h" -#include "../base/data_triangulation.h" +#include "../base/triangulation_annex_data.h" #include "../base/document.h" #include "../base/filepath_conv.h" #include "../base/mesh_utils.h" #include "../base/messenger.h" +#include "../base/point_cloud_data.h" #include "../base/property_builtins.h" #include "../base/tkernel_utils.h" #include "miniply.h" @@ -30,7 +33,6 @@ bool PlyReader::readFile(const FilePath& filepath, TaskProgress* /*progress*/) return false; // Reset internal data - m_isValidMesh = false; m_baseFilename = filepath.stem(); m_nodeCount = 0; m_vecNodeCoord.clear(); @@ -43,31 +45,32 @@ bool PlyReader::readFile(const FilePath& filepath, TaskProgress* /*progress*/) uint32_t faceIdxs[3] = {}; if (assumeTriangles) { miniply::PLYElement* faceElem = reader.get_element(reader.find_element(miniply::kPLYFaceElement)); - if (!faceElem) - return {}; - - assumeTriangles = faceElem->convert_list_to_fixed_size(faceElem->find_property("vertex_indices"), 3, faceIdxs); + if (faceElem) + assumeTriangles = faceElem->convert_list_to_fixed_size(faceElem->find_property("vertex_indices"), 3, faceIdxs); } + bool okLoad = true; bool gotVerts = false; bool gotFaces = false; while (reader.has_element() && (!gotVerts || !gotFaces)) { if (reader.element_is(miniply::kPLYVertexElement)) { - uint32_t propIdxs[3] = {}; - if (!reader.load_element() || !reader.find_pos(propIdxs)) + uint32_t prop3Idxs[3] = {}; + if (!reader.load_element() || !reader.find_pos(prop3Idxs)) { + okLoad = false; break; + } m_nodeCount = reader.num_rows(); m_vecNodeCoord.resize(m_nodeCount * 3); - reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::Float, m_vecNodeCoord.data()); - if (reader.find_normal(propIdxs)) { + reader.extract_properties(prop3Idxs, 3, miniply::PLYPropertyType::Float, m_vecNodeCoord.data()); + if (reader.find_normal(prop3Idxs)) { m_vecNormalCoord.resize(m_nodeCount * 3); - reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::Float, m_vecNormalCoord.data()); + reader.extract_properties(prop3Idxs, 3, miniply::PLYPropertyType::Float, m_vecNormalCoord.data()); } - if (reader.find_color(propIdxs)) { + if (reader.find_color(prop3Idxs)) { m_vecColorComponent.resize(m_nodeCount * 3); - reader.extract_properties(propIdxs, 3, miniply::PLYPropertyType::UChar, m_vecColorComponent.data()); + reader.extract_properties(prop3Idxs, 3, miniply::PLYPropertyType::UChar, m_vecColorComponent.data()); } //if (reader.find_texcoord(propIdxs)) { @@ -128,24 +131,28 @@ bool PlyReader::readFile(const FilePath& filepath, TaskProgress* /*progress*/) reader.next_element(); } // endwhile - auto fnCheckIndices = [](Span spanIndex, uint32_t nodeCount) { - for (int index : spanIndex) { - if (index < 0 || CppUtils::cmpGreaterEqual(index, nodeCount)) - return false; - } + return okLoad; +} + +TDF_LabelSequence PlyReader::transfer(DocumentPtr doc, TaskProgress* progress) +{ + TDF_Label entityLabel; + if (!m_vecNodeCoord.empty() && !m_vecIndex.empty()) + entityLabel = this->transferMesh(doc, progress); - return true; - }; + if (!m_vecNodeCoord.empty() && m_vecIndex.empty()) + entityLabel = this->transferPointCloud(doc, progress); - m_isValidMesh = gotVerts && gotFaces && fnCheckIndices(m_vecIndex, m_nodeCount); - return m_isValidMesh; + if (!entityLabel.IsNull()) { + TDataStd_Name::Set(entityLabel, filepathTo(m_baseFilename)); + return CafUtils::makeLabelSequence({ entityLabel }); + } + + return {}; } -TDF_LabelSequence PlyReader::transfer(DocumentPtr doc, TaskProgress* /*progress*/) +TDF_Label PlyReader::transferMesh(DocumentPtr doc, TaskProgress* /*progress*/) { - if (!m_isValidMesh) - return {}; - // Create target mesh const int triangleCount = CppUtils::safeStaticCast(m_vecIndex.size() / 3); Handle_Poly_Triangulation mesh = new Poly_Triangulation(m_nodeCount, triangleCount, false/*hasUvNodes*/); @@ -185,29 +192,49 @@ TDF_LabelSequence PlyReader::transfer(DocumentPtr doc, TaskProgress* /*progress* } // Insert mesh as a document entity - const TDF_Label entityLabel = doc->newEntityLabel(); - DataTriangulation::Set(entityLabel, mesh, vecColor); - TDataStd_Name::Set(entityLabel, filepathTo(m_baseFilename)); - return CafUtils::makeLabelSequence({ entityLabel }); + const TDF_Label entityLabel = doc->newEntityShapeLabel(); + doc->xcaf().setShape(entityLabel, BRepUtils::makeFace(mesh)); // IMPORTANT: pure mesh part marker! + TriangulationAnnexData::Set(entityLabel, vecColor); + return entityLabel; } -Span PlyFactoryReader::formats() const +TDF_Label PlyReader::transferPointCloud(DocumentPtr doc, TaskProgress* /*progress*/) { - static const Format arrayFormat[] = { Format_PLY }; - return arrayFormat; -} + const bool hasColors = !m_vecColorComponent.empty(); + const bool hasNormals = false; //!m_vecNormalCoord.empty(); + auto gfxPoints = new Graphic3d_ArrayOfPoints(m_vecNodeCoord.size(), hasColors, hasNormals); -std::unique_ptr PlyFactoryReader::create(Format format) const -{ - if (format == Format_PLY) - return std::make_unique(); + // Add nodes(vertices) into point cloud + for (int i = 0; CppUtils::cmpLess(i, m_vecNodeCoord.size()); i += 3) { + const auto& vec = m_vecNodeCoord; + const gp_Pnt node = { vec.at(i), vec.at(i + 1), vec.at(i + 2) }; + gfxPoints->AddVertex(node); + } - return {}; -} + if (hasColors) { + for (int i = 0; CppUtils::cmpLess(i, m_vecColorComponent.size()); i += 3) { + const auto& vec = m_vecColorComponent; + const Quantity_Color color{ + vec.at(i) / 255., vec.at(i + 1) / 255., vec.at(i + 2) / 255., + TKernelUtils::preferredRgbColorType() + }; + gfxPoints->SetVertexColor((i / 3) + 1, color); + } + } -std::unique_ptr PlyFactoryReader::createProperties(Format, PropertyGroup*) const -{ - return {}; +#if 0 + if (hasNormals) { + for (int i = 0; CppUtils::cmpLess(i, m_vecNormalCoord.size()); i += 3) { + const auto& vec = m_vecNormalCoord; + gfxPoints->SetVertexNormal((i / 3) + 1, vec.at(i), vec.at(i + 1), vec.at(i + 2)); + } + } +#endif + + // Insert point cloud as a document entity + const TDF_Label entityLabel = doc->newEntityLabel(); + PointCloudData::Set(entityLabel, gfxPoints); + return entityLabel; } } // namespace IO diff --git a/src/io_ply/io_ply_reader.h b/src/io_ply/io_ply_reader.h index 3642af8c..aac1018d 100644 --- a/src/io_ply/io_ply_reader.h +++ b/src/io_ply/io_ply_reader.h @@ -7,6 +7,8 @@ #pragma once #include "../base/io_reader.h" +#include "../base/io_single_format_factory.h" + #include namespace Mayo { @@ -19,10 +21,14 @@ class PlyReader : public Reader { TDF_LabelSequence transfer(DocumentPtr doc, TaskProgress* progress) override; void applyProperties(const PropertyGroup*) override {} + static std::unique_ptr createProperties(PropertyGroup*) { return {}; } + private: + TDF_Label transferMesh(DocumentPtr doc, TaskProgress* progress); + TDF_Label transferPointCloud(DocumentPtr doc, TaskProgress* progress); + FilePath m_baseFilename; uint32_t m_nodeCount = 0; - bool m_isValidMesh = false; std::vector m_vecNodeCoord; std::vector m_vecIndex; std::vector m_vecNormalCoord; @@ -30,12 +36,7 @@ class PlyReader : public Reader { }; // Provides factory to create PlyReader objects -class PlyFactoryReader : public FactoryReader { -public: - Span formats() const override; - std::unique_ptr create(Format format) const override; - std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; -}; +class PlyFactoryReader : public SingleFormatFactoryReader {}; } // namespace IO } // namespace Mayo diff --git a/src/io_ply/io_ply_writer.cpp b/src/io_ply/io_ply_writer.cpp index b729376a..3231db4e 100644 --- a/src/io_ply/io_ply_writer.cpp +++ b/src/io_ply/io_ply_writer.cpp @@ -6,8 +6,10 @@ #include "io_ply_writer.h" +#include "../base/caf_utils.h" #include "../base/cpp_utils.h" #include "../base/document.h" +#include "../base/label_data.h" #include "../base/io_system.h" #include "../base/math_utils.h" #include "../base/mesh_access.h" @@ -15,10 +17,11 @@ #include "../base/property_builtins.h" #include "../base/property_enumeration.h" #include "../base/task_progress.h" +#include "../base/tkernel_utils.h" #include -#include +#include #include #include @@ -90,23 +93,37 @@ bool PlyWriter::transfer(Span appItems, TaskProgress* pro // TODO Investigate task abort issue // Count number of faces for progress report - int faceCount = 0; + int count = 0; System::traverseUniqueItems(appItems, [&](const DocumentTreeNode& docTreeNode) { - if (docTreeNode.isLeaf()) - IMeshAccess_visitMeshes(docTreeNode, [&](const IMeshAccess&) { ++faceCount; }); + if (docTreeNode.isLeaf()) { + IMeshAccess_visitMeshes(docTreeNode, [&](const IMeshAccess&) { ++count; }); + if (findLabelDataFlags(docTreeNode.label()) & LabelData_HasPointCloudData) + ++count; + } }); // Record face meshes - int iFace = 0; + int iCount = 0; System::traverseUniqueItems(appItems, [&](const DocumentTreeNode& docTreeNode) { if (docTreeNode.isLeaf() && !progress->isAbortRequested()) { IMeshAccess_visitMeshes(docTreeNode, [&](const IMeshAccess& mesh) { this->addMesh(mesh); - progress->setValue(MathUtils::mappedValue(++iFace, 0, faceCount, 0, 100)); + progress->setValue(MathUtils::toPercent(++iCount, 0, count)); }); } }); + // Record point clouds + System::traverseUniqueItems(appItems, [&](const DocumentTreeNode& docTreeNode) { + if (docTreeNode.isLeaf() + && (findLabelDataFlags(docTreeNode.label()) & LabelData_HasPointCloudData) + && !progress->isAbortRequested()) + { + this->addPointCloud(CafUtils::findAttribute(docTreeNode.label())); + progress->setValue(MathUtils::toPercent(++iCount, 0, count)); + } + }); + return true; } @@ -175,7 +192,7 @@ bool PlyWriter::writeFile(const FilePath& filepath, TaskProgress* progress) auto fnUpdateProgress = [&]{ ++iElement; if (iElement % 50 == 0) { - progress->setValue(MathUtils::mappedValue(iElement, 0, elementCount, 0, 100)); + progress->setValue(MathUtils::toPercent(iElement, 0, elementCount)); if (progress->isAbortRequested()) return false; } @@ -254,44 +271,46 @@ void PlyWriter::addMesh(const IMeshAccess& mesh) } for (int i = 1; i <= triangulation->NbNodes(); ++i) { - const gp_Pnt node = triangulation->Node(i).Transformed(mesh.location()); - const Vertex vertex{ float(node.X()), float(node.Y()), float(node.Z()) }; + const Vertex vertex = PlyWriter::toVertex(triangulation->Node(i).Transformed(mesh.location())); m_vecNode.push_back(std::move(vertex)); } if (m_params.writeColors) { - // Helper color conversion function - auto fnColor = [](const Quantity_Color& c) -> Color { - return { uint8_t(c.Red() * 255), uint8_t(c.Green() * 255), uint8_t(c.Blue() * 255) }; - }; for (int i = 0; i < triangulation->NbNodes(); ++i) { const std::optional nodeColor = mesh.nodeColor(i); const Quantity_Color& defaultNodeColor = m_params.defaultColor.GetRGB(); - m_vecNodeColor.push_back(fnColor(nodeColor ? nodeColor.value() : defaultNodeColor)); + m_vecNodeColor.push_back(PlyWriter::toColor(nodeColor ? nodeColor.value() : defaultNodeColor)); } } } -Span PlyFactoryWriter::formats() const +void PlyWriter::addPointCloud(const PointCloudDataPtr& pntCloud) { - static const Format arrayFormat[] = { Format_PLY }; - return arrayFormat; + const Handle(Graphic3d_ArrayOfPoints)& points = pntCloud->points(); + const int pntCount = points->VertexNumber(); + for (int i = 1; i <= pntCount; ++i) { + const Vertex vertex = PlyWriter::toVertex(points->Vertice(i)); + m_vecNode.push_back(std::move(vertex)); + } + + if (m_params.writeColors) { + const bool hasColors = points->HasVertexColors(); + for (int i = 1; i <= pntCount; ++i) { + const Quantity_Color pntColor = hasColors ? points->VertexColor(i) : m_params.defaultColor.GetRGB(); + m_vecNodeColor.push_back(PlyWriter::toColor(pntColor)); + } + } } -std::unique_ptr PlyFactoryWriter::create(Format format) const +PlyWriter::Vertex PlyWriter::toVertex(const gp_Pnt& pnt) { - if (format == Format_PLY) - return std::make_unique(); - - return {}; + return Vertex{ float(pnt.X()), float(pnt.Y()), float(pnt.Z()) }; } -std::unique_ptr PlyFactoryWriter::createProperties(Format format, PropertyGroup* parentGroup) const +PlyWriter::Color PlyWriter::toColor(const Quantity_Color& c) { - if (format == Format_PLY) - return PlyWriter::createProperties(parentGroup); - - return {}; + const Quantity_Color cc = TKernelUtils::toLinearRgbColor(c); + return { uint8_t(cc.Red() * 255), uint8_t(cc.Green() * 255), uint8_t(cc.Blue() * 255) }; } } // namespace IO diff --git a/src/io_ply/io_ply_writer.h b/src/io_ply/io_ply_writer.h index f6167f1b..eda3dd74 100644 --- a/src/io_ply/io_ply_writer.h +++ b/src/io_ply/io_ply_writer.h @@ -8,6 +8,9 @@ #include "../base/document_ptr.h" #include "../base/io_writer.h" +#include "../base/io_single_format_factory.h" +#include "../base/point_cloud_data.h" + #include #include @@ -16,7 +19,7 @@ namespace Mayo { class IMeshAccess; } namespace Mayo { namespace IO { -// Writer for PLY file format based on the msh_ply library +// Writer for PLY file format class PlyWriter : public Writer { public: bool transfer(Span appItems, TaskProgress* progress) override; @@ -43,7 +46,12 @@ class PlyWriter : public Writer { struct Vertex { float x; float y; float z; }; struct Color { uint8_t red; uint8_t green; uint8_t blue; }; struct Face { int32_t v1; int32_t v2; int32_t v3; }; + + static Vertex toVertex(const gp_Pnt& pnt); + static Color toColor(const Quantity_Color& c); + void addMesh(const IMeshAccess& mesh); + void addPointCloud(const PointCloudDataPtr& pntCloud); class Properties; Parameters m_params; @@ -53,12 +61,7 @@ class PlyWriter : public Writer { }; // Provides factory to create PlyWriter objects -class PlyFactoryWriter : public FactoryWriter { -public: - Span formats() const override; - std::unique_ptr create(Format format) const override; - std::unique_ptr createProperties(Format format, PropertyGroup* parentGroup) const override; -}; +class PlyFactoryWriter : public SingleFormatFactoryWriter {}; } // namespace IO } // namespace Mayo diff --git a/src/io_ply/miniply.cpp b/src/io_ply/miniply.cpp index 04a2dfeb..bd0097d7 100644 --- a/src/io_ply/miniply.cpp +++ b/src/io_ply/miniply.cpp @@ -1,18 +1,14 @@ /* MIT License - Copyright (c) 2019 Vilya Harvey - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -558,7 +554,7 @@ namespace miniply { uint32_t propIdx = listPropIdx + 1 + i; PLYProperty& itemProp = properties[propIdx]; - snprintf(nameBuf, sizeof(nameBuf), "%s_%u", oldListProp.name.c_str(), i); + snprintf(nameBuf, nameBufSize, "%s_%u", oldListProp.name.c_str(), i); itemProp.name = nameBuf; itemProp.type = oldListProp.type; itemProp.countType = PLYPropertyType::None; @@ -1320,13 +1316,13 @@ namespace miniply { bool PLYReader::find_color(uint32_t propIdxs[3]) const { return find_properties(propIdxs, 3, "r", "g", "b") || - find_properties(propIdxs, 3, "red", "green", "blue"); + find_properties(propIdxs, 3, "red", "green", "blue") || + find_properties(propIdxs, 3, "diffuse_red", "diffuse_green", "diffuse_blue"); } - bool PLYReader::find_indices(uint32_t propIdxs[1]) const { - return find_properties(propIdxs, 1, "vertex_indices") || + return find_properties(propIdxs, 1, "vertex_indices") || find_properties(propIdxs, 1, "vertex_index"); } @@ -1608,7 +1604,7 @@ namespace miniply { bool PLYReader::load_fixed_size_element(PLYElement& elem) { - size_t numBytes = elem.count * elem.rowStride; + size_t numBytes = static_cast(elem.count) * elem.rowStride; m_elementData.resize(numBytes); @@ -1679,7 +1675,7 @@ namespace miniply { bool PLYReader::load_variable_size_element(PLYElement& elem) { - m_elementData.resize(elem.count * elem.rowStride); + m_elementData.resize(static_cast(elem.count) * elem.rowStride); // Preallocate enough space for each row in the property to contain three // items. This is based on the assumptions that (a) the most common use for diff --git a/src/io_ply/miniply.h b/src/io_ply/miniply.h index d5295e58..1995696a 100644 --- a/src/io_ply/miniply.h +++ b/src/io_ply/miniply.h @@ -1,18 +1,14 @@ /* MIT License - Copyright (c) 2019 Vilya Harvey - Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE @@ -22,6 +18,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#ifndef MINIPLY_H +#define MINIPLY_H + #include #include #include @@ -310,3 +309,5 @@ namespace miniply { uint32_t triangulate_polygon(uint32_t n, const float pos[], uint32_t numVerts, const int indices[], int dst[]); } // namespace miniply + +#endif // MINIPLY_H diff --git a/src/app/measure_display.cpp b/src/measure/measure_display.cpp similarity index 78% rename from src/app/measure_display.cpp rename to src/measure/measure_display.cpp index 3c69ec59..ade95d89 100644 --- a/src/app/measure_display.cpp +++ b/src/measure/measure_display.cpp @@ -6,14 +6,17 @@ #include "measure_display.h" -#include "app_module.h" +#include "../base/text_id.h" +#include "../base/unit_system.h" #include "measure_tool.h" -#include "qstring_conv.h" -#include "qstring_utils.h" #include #include #include +#include +#include +#include +#include #include @@ -57,6 +60,27 @@ std::unique_ptr BaseMeasureDisplay::createEmptySumFrom(MeasureT } } +void BaseMeasureDisplay::adaptGraphics(const Handle_Graphic3d_GraphicDriver& driver) +{ + const auto openGlDriver = Handle_OpenGl_GraphicDriver::DownCast(driver); + const auto openGlContext = openGlDriver ? openGlDriver->GetSharedContext() : nullptr; + if (!openGlContext) + return; + + const bool useVbo = openGlContext->ToUseVbo(); + for (int i = 0; i < this->graphicsObjectsCount(); ++i) { + auto gfxText = Handle(AIS_TextLabel)::DownCast(this->graphicsObjectAt(i)); + if (gfxText) { + // NOTE + // Usage of Aspect_TODT_SUBTITLE is causing a crash when VBO are not available(eg because + // of too old OpenGL version) + gfxText->SetDisplayType(useVbo ? Aspect_TODT_SUBTITLE : Aspect_TODT_NORMAL); + gfxText->SetColor(useVbo ? Quantity_NOC_WHITE : Quantity_NOC_BLACK); + gfxText->SetTransparency(useVbo ? 0.2 : 0.); + } + } +} + void BaseMeasureDisplay::sumAdd(const IMeasureDisplay& /*other*/) { ++m_sumCount; @@ -67,7 +91,7 @@ std::string_view BaseMeasureDisplay::sumTextOr(std::string_view singleItemText) return m_sumCount > 1 ? MeasureDisplayI18N::textIdTr("Sum") : singleItemText; } -std::string BaseMeasureDisplay::text(const gp_Pnt& pnt, const MeasureConfig& config) +std::string BaseMeasureDisplay::text(const gp_Pnt& pnt, const MeasureDisplayConfig& config) { auto trPntX = UnitSystem::translateLength(pnt.X() * Quantity_Millimeter, config.lengthUnit); auto trPntY = UnitSystem::translateLength(pnt.Y() * Quantity_Millimeter, config.lengthUnit); @@ -76,34 +100,39 @@ std::string BaseMeasureDisplay::text(const gp_Pnt& pnt, const MeasureConfig& con MeasureDisplayI18N::textIdTr("(X{0} " "Y{1} " "Z{2}){3}"), - text(trPntX), text(trPntY), text(trPntZ), + text(trPntX, config), text(trPntY, config), text(trPntZ, config), trPntX.strUnit ); return str; } -std::string BaseMeasureDisplay::text(double value) +std::string BaseMeasureDisplay::text(double value, const MeasureDisplayConfig& config) { - const QStringUtils::TextOptions textOpts = AppModule::get()->defaultTextOptions(); - return to_stdString(QStringUtils::text(value, textOpts)); + return to_stdString(value, config.doubleToStringOptions); } -std::string BaseMeasureDisplay::graphicsText(const gp_Pnt& pnt, const MeasureConfig& config) +std::string BaseMeasureDisplay::graphicsText(const gp_Pnt& pnt, const MeasureDisplayConfig& 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)) + text(UnitSystem::translateLength(pnt.X() * Quantity_Millimeter, config.lengthUnit), config), + text(UnitSystem::translateLength(pnt.Y() * Quantity_Millimeter, config.lengthUnit), config), + text(UnitSystem::translateLength(pnt.Z() * Quantity_Millimeter, config.lengthUnit), config) ); return BaseMeasureDisplay; } +void BaseMeasureDisplay::adaptScale(const Handle_AIS_TextLabel& gfxText, const MeasureDisplayConfig& config) +{ + static const Prs3d_TextAspect defaultTextAspect; + gfxText->SetHeight(defaultTextAspect.Height() * config.devicePixelRatio); +} + 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); + auto gfxText = Handle(AIS_TextLabel)::DownCast(gfxObject); if (gfxText) { gfxText->SetDisplayType(Aspect_TODT_SUBTITLE); gfxText->SetColorSubTitle(Quantity_NOC_BLACK); @@ -128,10 +157,11 @@ MeasureDisplayVertex::MeasureDisplayVertex(const gp_Pnt& pnt) BaseMeasureDisplay::applyGraphicsDefaults(this); } -void MeasureDisplayVertex::update(const MeasureConfig& config) +void MeasureDisplayVertex::update(const MeasureDisplayConfig& config) { this->setText(BaseMeasureDisplay::text(m_pnt, config)); m_gfxText->SetText(to_OccExtString(BaseMeasureDisplay::graphicsText(m_pnt, config))); + BaseMeasureDisplay::adaptScale(m_gfxText, config); } GraphicsObjectPtr MeasureDisplayVertex::graphicsObjectAt(int i) const @@ -149,18 +179,20 @@ MeasureDisplayCircleCenter::MeasureDisplayCircleCenter(const MeasureCircle& circ m_gfxText(new AIS_TextLabel) { m_gfxText->SetPosition(m_circle.Location()); - if (circle.isArc) { + if (circle.isArc) m_gfxCircle = new AIS_Circle(new Geom_Circle(m_circle)); - m_gfxCircle->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); - } BaseMeasureDisplay::applyGraphicsDefaults(this); + // BEWARE LineAspect() is created indirectly by SetColor() call in applyGraphicsDefaults() function + if (m_gfxCircle) + m_gfxCircle->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); } -void MeasureDisplayCircleCenter::update(const MeasureConfig& config) +void MeasureDisplayCircleCenter::update(const MeasureDisplayConfig& config) { this->setText(BaseMeasureDisplay::text(m_circle.Location(), config)); m_gfxText->SetText(to_OccExtString(BaseMeasureDisplay::graphicsText(m_circle.Location(), config))); + BaseMeasureDisplay::adaptScale(m_gfxText, config); } int MeasureDisplayCircleCenter::graphicsObjectsCount() const @@ -195,20 +227,21 @@ MeasureDisplayCircleDiameter::MeasureDisplayCircleDiameter(const MeasureCircle& m_gfxDiameterText->SetPosition(m_circle.Location()); BaseMeasureDisplay::applyGraphicsDefaults(this); + // BEWARE LineAspect() is created indirectly by SetColor() call in applyGraphicsDefaults() function m_gfxCircle->Attributes()->LineAspect()->SetTypeOfLine(Aspect_TOL_DOT); } -void MeasureDisplayCircleDiameter::update(const MeasureConfig& config) +void MeasureDisplayCircleDiameter::update(const MeasureDisplayConfig& config) { const QuantityLength diameter = 2 * m_circle.Radius() * Quantity_Millimeter; const auto trDiameter = UnitSystem::translateLength(diameter, config.lengthUnit); - const auto strDiameter = BaseMeasureDisplay::text(trDiameter); + const auto strDiameter = BaseMeasureDisplay::text(trDiameter, config); this->setText(fmt::format( MeasureDisplayI18N::textIdTr("Diameter: {0}{1}"), strDiameter, trDiameter.strUnit - )); + )); m_gfxDiameterText->SetText(to_OccExtString(fmt::format(MeasureDisplayI18N::textIdTr(" Ø{0}"), strDiameter))); - + BaseMeasureDisplay::adaptScale(m_gfxDiameterText, config); } GraphicsObjectPtr MeasureDisplayCircleDiameter::graphicsObjectAt(int i) const @@ -244,18 +277,19 @@ MeasureDisplayMinDistance::MeasureDisplayMinDistance(const MeasureMinDistance& d BaseMeasureDisplay::applyGraphicsDefaults(this); } -void MeasureDisplayMinDistance::update(const MeasureConfig& config) +void MeasureDisplayMinDistance::update(const MeasureDisplayConfig& config) { const auto trLength = UnitSystem::translateLength(m_dist.value, config.lengthUnit); - const auto strLength = BaseMeasureDisplay::text(trLength); + const auto strLength = BaseMeasureDisplay::text(trLength, config); 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)); + BaseMeasureDisplay::adaptScale(m_gfxDistText, config); } GraphicsObjectPtr MeasureDisplayMinDistance::graphicsObjectAt(int i) const @@ -294,12 +328,13 @@ MeasureDisplayAngle::MeasureDisplayAngle(MeasureAngle angle) m_gfxAngleText->SetPosition(ElCLib::Value(param1 + std::abs((param2 - param1) / 2.), geomCircle->Circ())); } -void MeasureDisplayAngle::update(const MeasureConfig& config) +void MeasureDisplayAngle::update(const MeasureDisplayConfig& config) { const auto trAngle = UnitSystem::translateAngle(m_angle.value, config.angleUnit); - const auto strAngle = BaseMeasureDisplay::text(trAngle); + const auto strAngle = BaseMeasureDisplay::text(trAngle, config); this->setText(fmt::format(MeasureDisplayI18N::textIdTr("Angle: {0}{1}"), strAngle, trAngle.strUnit)); m_gfxAngleText->SetText(to_OccExtString(" " + strAngle)); + BaseMeasureDisplay::adaptScale(m_gfxAngleText, config); } int MeasureDisplayAngle::graphicsObjectsCount() const @@ -328,15 +363,15 @@ MeasureDisplayLength::MeasureDisplayLength(QuantityLength length) { } -void MeasureDisplayLength::update(const MeasureConfig& config) +void MeasureDisplayLength::update(const MeasureDisplayConfig& 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), + BaseMeasureDisplay::text(trLength, config), trLength.strUnit - )); + )); } void MeasureDisplayLength::sumAdd(const IMeasureDisplay& other) @@ -355,15 +390,15 @@ MeasureDisplayArea::MeasureDisplayArea(QuantityArea area) { } -void MeasureDisplayArea::update(const MeasureConfig& config) +void MeasureDisplayArea::update(const MeasureDisplayConfig& 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), + BaseMeasureDisplay::text(trArea, config), trArea.strUnit - )); + )); } void MeasureDisplayArea::sumAdd(const IMeasureDisplay& other) diff --git a/src/app/measure_display.h b/src/measure/measure_display.h similarity index 71% rename from src/app/measure_display.h rename to src/measure/measure_display.h index addb37b1..b5573c5c 100644 --- a/src/app/measure_display.h +++ b/src/measure/measure_display.h @@ -7,12 +7,14 @@ #pragma once #include "measure_tool.h" +#include "../base/string_conv.h" #include "../graphics/graphics_object_ptr.h" #include #include #include #include +#include #include #include @@ -22,21 +24,44 @@ namespace Mayo { -struct MeasureConfig { +struct MeasureDisplayConfig { LengthUnit lengthUnit = LengthUnit::Millimeter; AngleUnit angleUnit = AngleUnit::Degree; AreaUnit areaUnit = AreaUnit::SquareMillimeter; + DoubleToStringOptions doubleToStringOptions; + double devicePixelRatio = 1.; }; // Provides an interface to textual/graphics representation of a measure class IMeasureDisplay { public: virtual ~IMeasureDisplay() = default; - virtual void update(const MeasureConfig& config) = 0; + + // Update the textual and graphical representations regarding some display configuration + virtual void update(const MeasureDisplayConfig& config) = 0; + + // Textual representation of the measure virtual std::string text() const = 0; + + // Count of objects for the 3D graphics representation of the measure virtual int graphicsObjectsCount() const = 0; + + // 3D graphics object at index 'i' + // Valid index is within [0 .. graphicsObjectsCount[ virtual GraphicsObjectPtr graphicsObjectAt(int i) const = 0; + + // Adapt 3D graphics objects to what is supported by 'driver' + // This function must be called before adding the graphical objects to the 3D scene(Mayo::GraphicsScene + // or AIS_InteractiveContext) + virtual void adaptGraphics(const Handle_Graphic3d_GraphicDriver& driver) = 0; + + // Whether "sum" mode is supported by the measure display + // This is relevant when multiple measureable 3D objects are selected. The cumulative sum of + // each measure is computed and made available in the textual and/or graphics representations virtual bool isSumSupported() const = 0; + + // Add 'other' to this measure display(see isSumSupported()) + // 'other' should be of the same base type as this IMeasureDisplay object virtual void sumAdd(const IMeasureDisplay& other) = 0; }; @@ -49,6 +74,8 @@ class BaseMeasureDisplay : public IMeasureDisplay { static std::unique_ptr createFrom(MeasureType type, const MeasureValue& value); static std::unique_ptr createEmptySumFrom(MeasureType type); + void adaptGraphics(const Handle_Graphic3d_GraphicDriver& driver) override; + bool isSumSupported() const override { return false; } void sumAdd(const IMeasureDisplay& other) override; @@ -58,9 +85,10 @@ class BaseMeasureDisplay : public IMeasureDisplay { 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 std::string text(const gp_Pnt& pnt, const MeasureDisplayConfig& config); + static std::string text(double value, const MeasureDisplayConfig& config); + static std::string graphicsText(const gp_Pnt& pnt, const MeasureDisplayConfig& config); + static void adaptScale(const Handle_AIS_TextLabel& gfxText, const MeasureDisplayConfig& config); static void applyGraphicsDefaults(IMeasureDisplay* measureDisplay); @@ -76,7 +104,7 @@ class BaseMeasureDisplay : public IMeasureDisplay { class MeasureDisplayVertex : public BaseMeasureDisplay { public: MeasureDisplayVertex(const gp_Pnt& pnt); - void update(const MeasureConfig& config) override; + void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override { return 1; } GraphicsObjectPtr graphicsObjectAt(int i) const override; @@ -92,7 +120,7 @@ class MeasureDisplayVertex : public BaseMeasureDisplay { class MeasureDisplayCircleCenter : public BaseMeasureDisplay { public: MeasureDisplayCircleCenter(const MeasureCircle& circle); - void update(const MeasureConfig& config) override; + void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override; GraphicsObjectPtr graphicsObjectAt(int i) const override; @@ -110,7 +138,7 @@ class MeasureDisplayCircleCenter : public BaseMeasureDisplay { class MeasureDisplayCircleDiameter : public BaseMeasureDisplay { public: MeasureDisplayCircleDiameter(const MeasureCircle& circle); - void update(const MeasureConfig& config) override; + void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override { return 3; } GraphicsObjectPtr graphicsObjectAt(int i) const override; @@ -130,7 +158,7 @@ class MeasureDisplayCircleDiameter : public BaseMeasureDisplay { class MeasureDisplayMinDistance : public BaseMeasureDisplay { public: MeasureDisplayMinDistance(const MeasureMinDistance& dist); - void update(const MeasureConfig& config) override; + void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override { return 4; } GraphicsObjectPtr graphicsObjectAt(int i) const override; @@ -149,7 +177,7 @@ class MeasureDisplayMinDistance : public BaseMeasureDisplay { class MeasureDisplayAngle : public BaseMeasureDisplay { public: MeasureDisplayAngle(MeasureAngle angle); - void update(const MeasureConfig& config) override; + void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override; GraphicsObjectPtr graphicsObjectAt(int i) const override; @@ -168,7 +196,7 @@ class MeasureDisplayAngle : public BaseMeasureDisplay { class MeasureDisplayLength : public BaseMeasureDisplay { public: MeasureDisplayLength(QuantityLength length); - void update(const MeasureConfig& config) override; + void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override { return 0; } GraphicsObjectPtr graphicsObjectAt(int /*i*/) const override { return {}; } @@ -186,7 +214,7 @@ class MeasureDisplayLength : public BaseMeasureDisplay { class MeasureDisplayArea : public BaseMeasureDisplay { public: MeasureDisplayArea(QuantityArea area); - void update(const MeasureConfig& config) override; + void update(const MeasureDisplayConfig& config) override; int graphicsObjectsCount() const override { return 0; } GraphicsObjectPtr graphicsObjectAt(int /*i*/) const override { return {}; } @@ -195,7 +223,6 @@ class MeasureDisplayArea : public BaseMeasureDisplay { private: QuantityArea m_area; - int m_sumCount = 0; }; } // namespace Mayo diff --git a/src/app/measure_tool.cpp b/src/measure/measure_tool.cpp similarity index 95% rename from src/app/measure_tool.cpp rename to src/measure/measure_tool.cpp index 8f9c007c..79d56c5e 100644 --- a/src/app/measure_tool.cpp +++ b/src/measure/measure_tool.cpp @@ -12,7 +12,6 @@ MeasureValue IMeasureTool_computeValue( const IMeasureTool& tool, MeasureType type, const GraphicsOwnerPtr& owner) { MeasureValue value; - switch (type) { case MeasureType::VertexPosition: return tool.vertexPosition(owner); @@ -23,9 +22,9 @@ MeasureValue IMeasureTool_computeValue( return tool.length(owner); case MeasureType::Area: return tool.area(owner); + default: + return value; } // endswitch - - return value; } MeasureValue IMeasureTool_computeValue( @@ -35,15 +34,14 @@ MeasureValue IMeasureTool_computeValue( const GraphicsOwnerPtr& owner2) { MeasureValue value; - switch (type) { case MeasureType::MinDistance: return tool.minDistance(owner1, owner2); case MeasureType::Angle: return tool.angle(owner1, owner2); + default: + return value; } // endswitch - - return value; } bool MeasureValue_isValid(const MeasureValue& value) diff --git a/src/app/measure_tool.h b/src/measure/measure_tool.h similarity index 98% rename from src/app/measure_tool.h rename to src/measure/measure_tool.h index 06197edc..428d8e38 100644 --- a/src/app/measure_tool.h +++ b/src/measure/measure_tool.h @@ -82,7 +82,7 @@ class IMeasureError { // 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 + MeasureNone, // WARNING: ensure this is the first value type in the variant gp_Pnt, MeasureCircle, MeasureMinDistance, diff --git a/src/app/measure_tool_brep.cpp b/src/measure/measure_tool_brep.cpp similarity index 67% rename from src/app/measure_tool_brep.cpp rename to src/measure/measure_tool_brep.cpp index b34eef5c..608649f1 100644 --- a/src/app/measure_tool_brep.cpp +++ b/src/measure/measure_tool_brep.cpp @@ -7,6 +7,7 @@ #include "measure_tool_brep.h" #include "../base/geom_utils.h" +#include "../base/math_utils.h" #include "../base/text_id.h" #include "../graphics/graphics_shape_object_driver.h" @@ -46,6 +47,7 @@ enum class ErrorCode { NotVertex, NotCircularEdge, NotBRepShape, + NotGeometricOrPolygonEdge, MinDistanceFailure, NotAllEdges, NotLinearEdge, @@ -53,19 +55,21 @@ enum class ErrorCode { ParallelEdges }; -template +template class BRepMeasureError : public IMeasureError { MAYO_DECLARE_TEXT_ID_FUNCTIONS(Mayo::BRepMeasureError) public: std::string_view message() const override { - switch (ERRCODE) { + switch (Err) { 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::NotGeometricOrPolygonEdge: + return textIdTr("Entity must be a geometric or polygon edge"); case ErrorCode::MinDistanceFailure: return textIdTr("Computation of minimum distance failed"); case ErrorCode::NotAllEdges: @@ -82,17 +86,17 @@ class BRepMeasureError : public IMeasureError { } }; -template void throwErrorIf(bool cond) +template void throwErrorIf(bool cond) { if (cond) - throw BRepMeasureError(); + throw BRepMeasureError(); } -const TopoDS_Shape& getShape(const GraphicsOwnerPtr& owner) +const TopoDS_Shape getShape(const GraphicsOwnerPtr& owner) { static const TopoDS_Shape nullShape; auto brepOwner = Handle_StdSelect_BRepOwner::DownCast(owner); - return brepOwner ? brepOwner->Shape() : nullShape; + return brepOwner ? brepOwner->Shape().Moved(owner->Location()) : nullShape; } } // namespace @@ -123,9 +127,10 @@ Span MeasureToolBRep::selectionModes(MeasureT static const GraphicsObjectSelectionMode modes[] = { AIS_Shape::SelectionMode(TopAbs_FACE) }; return modes; } + default: { + return {}; + } } // endswitch - - return {}; } bool MeasureToolBRep::supports(const GraphicsObjectPtr& object) const @@ -141,23 +146,48 @@ bool MeasureToolBRep::supports(MeasureType type) const 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()); + return brepVertexPosition(getShape(owner)); } MeasureCircle MeasureToolBRep::circle(const GraphicsOwnerPtr& owner) const { - auto fnThrowErrorIf = [](bool cond) { - throwErrorIf(cond); - }; + return brepCircle(getShape(owner)); +} + +MeasureMinDistance MeasureToolBRep::minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +{ + return brepMinDistance(getShape(owner1), getShape(owner2)); +} - const TopoDS_Shape& shape = getShape(owner); - fnThrowErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_EDGE); +MeasureAngle MeasureToolBRep::angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +{ + return brepAngle(getShape(owner1), getShape(owner2)); +} + +QuantityLength MeasureToolBRep::length(const GraphicsOwnerPtr& owner) const +{ + return brepLength(getShape(owner)); +} + +QuantityArea MeasureToolBRep::area(const GraphicsOwnerPtr& owner) const +{ + const TopoDS_Shape shape = getShape(owner); + throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_FACE); + GProp_GProps gprops; + BRepGProp::SurfaceProperties(TopoDS::Face(shape), gprops); + const double area = gprops.Mass(); + return area * Quantity_SquareMillimeter; +} + +gp_Pnt MeasureToolBRep::brepVertexPosition(const TopoDS_Shape& shape) +{ + throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_VERTEX); + return BRep_Tool::Pnt(TopoDS::Vertex(shape)); +} - const BRepAdaptor_Curve curve(TopoDS::Edge(shape)); +MeasureCircle MeasureToolBRep::brepCircleFromGeometricEdge(const TopoDS_Edge& edge) +{ + const BRepAdaptor_Curve curve(edge); std::optional circle; if (curve.GetType() == GeomAbs_Circle) { circle = curve.Circle(); @@ -171,13 +201,13 @@ MeasureCircle MeasureToolBRep::circle(const GraphicsOwnerPtr& owner) const // 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); + throwErrorIf(!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()); + throwErrorIf(!makeCirc.IsDone()); circle = makeCirc.Value()->Circ(); } @@ -185,27 +215,65 @@ MeasureCircle MeasureToolBRep::circle(const GraphicsOwnerPtr& owner) const // dist(pntSample, pntCircleCenter) - circleRadius < tolerance { const GCPnts_QuasiUniformAbscissa pnts(curve, 64); - fnThrowErrorIf(!pnts.IsDone()); + throwErrorIf(!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); + throwErrorIf(std::abs(dist - circle->Radius()) > 1e-4); } } } - fnThrowErrorIf(!circle); + throwErrorIf(!circle); MeasureCircle result; - result.pntAnchor = GeomUtils::d0(curve, curve.FirstParameter()).Transformed(owner->Location()); + result.pntAnchor = GeomUtils::d0(curve, curve.FirstParameter()); result.isArc = !curve.IsClosed(); - result.value = circle->Transformed(owner->Location()); + result.value = circle.value(); return result; } -MeasureMinDistance MeasureToolBRep::minDistance(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +MeasureCircle MeasureToolBRep::brepCircleFromPolygonEdge(const TopoDS_Edge& edge) +{ + TopLoc_Location loc; + const Handle(Poly_Polygon3D)& polyline = BRep_Tool::Polygon3D(edge, loc); + throwErrorIf(polyline.IsNull() || polyline->NbNodes() < 7); + // Try to create a circle from 3 sample points + const GC_MakeCircle makeCirc( + polyline->Nodes().First(), + polyline->Nodes().Value(1 + polyline->NbNodes() / 3), + polyline->Nodes().Value(1 + 2 * polyline->NbNodes() / 3) + ); + throwErrorIf(!makeCirc.IsDone()); + const gp_Circ circle = makeCirc.Value()->Circ(); + + // Check for all points that: + // dist(pnt, pntCircleCenter) - circleRadius < tolerance + const double tol = !MathUtils::fuzzyIsNull(polyline->Deflection()) ? 1.5 * polyline->Deflection() : 1e-4; + for (const gp_Pnt& pnt : polyline->Nodes()) { + const double dist = pnt.Distance(circle.Location()); + throwErrorIf(std::abs(dist - circle.Radius()) > tol); + } + + MeasureCircle result; + result.pntAnchor = polyline->Nodes().First().Transformed(loc); + result.isArc = !polyline->Nodes().First().IsEqual(polyline->Nodes().Last(), Precision::Confusion()); + result.value = circle; + return result; +} + +MeasureCircle MeasureToolBRep::brepCircle(const TopoDS_Shape& shape) +{ + throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_EDGE); + const TopoDS_Edge& edge = TopoDS::Edge(shape); + if (BRep_Tool::IsGeometric(edge)) + return MeasureToolBRep::brepCircleFromGeometricEdge(edge); + else + return MeasureToolBRep::brepCircleFromPolygonEdge(edge); +} + +MeasureMinDistance MeasureToolBRep::brepMinDistance( + const TopoDS_Shape& shape1, const TopoDS_Shape& shape2) { - const TopoDS_Shape shape1 = getShape(owner1).Moved(owner1->Location()); - const TopoDS_Shape shape2 = getShape(owner2).Moved(owner2->Location()); throwErrorIf(shape1.IsNull()); throwErrorIf(shape2.IsNull()); @@ -219,12 +287,10 @@ MeasureMinDistance MeasureToolBRep::minDistance(const GraphicsOwnerPtr& owner1, return distResult; } -MeasureAngle MeasureToolBRep::angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const +MeasureAngle MeasureToolBRep::brepAngle(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2) { MeasureAngle angleResult; - const TopoDS_Shape& shape1 = getShape(owner1); - const TopoDS_Shape& shape2 = getShape(owner2); throwErrorIf(shape1.IsNull()); throwErrorIf(shape2.IsNull()); @@ -275,9 +341,9 @@ MeasureAngle MeasureToolBRep::angle(const GraphicsOwnerPtr& owner1, const Graphi // Compute angle by delegating to PrsDim_AngleDimension PrsDim_AngleDimension dimAngle(edge1, edge2); - angleResult.pnt1 = dimAngle.FirstPoint().Transformed(owner1->Location()); - angleResult.pnt2 = dimAngle.SecondPoint().Transformed(owner2->Location()); - angleResult.pntCenter = dimAngle.CenterPoint().Transformed(owner1->Location()); + angleResult.pnt1 = dimAngle.FirstPoint(); + angleResult.pnt2 = dimAngle.SecondPoint(); + angleResult.pntCenter = dimAngle.CenterPoint(); const gp_Vec vec1(angleResult.pntCenter, angleResult.pnt1); const gp_Vec vec2(angleResult.pntCenter, angleResult.pnt2); angleResult.value = vec1.Angle(vec2) * Quantity_Radian; @@ -285,23 +351,28 @@ MeasureAngle MeasureToolBRep::angle(const GraphicsOwnerPtr& owner1, const Graphi return angleResult; } -QuantityLength MeasureToolBRep::length(const GraphicsOwnerPtr& owner) const +QuantityLength MeasureToolBRep::brepLength(const TopoDS_Shape& shape) { - const TopoDS_Shape& shape = getShape(owner); throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_EDGE); - const BRepAdaptor_Curve curve(TopoDS::Edge(shape)); - const double len = GCPnts_AbscissaPoint::Length(curve, 1e-6); - return len * Quantity_Millimeter; -} + const TopoDS_Edge& edge = TopoDS::Edge(shape); + if (BRep_Tool::IsGeometric(edge)) { + const BRepAdaptor_Curve curve(TopoDS::Edge(shape)); + const double len = GCPnts_AbscissaPoint::Length(curve, 1e-6); + return len * Quantity_Millimeter; + } + else { + TopLoc_Location loc; + const Handle(Poly_Polygon3D)& polyline = BRep_Tool::Polygon3D(edge, loc); + throwErrorIf(polyline.IsNull()); + double len = 0.; + for (int i = 2; i <= polyline->NbNodes(); ++i) { + const gp_Pnt& pnt1 = polyline->Nodes().Value(i - 1); + const gp_Pnt& pnt2 = polyline->Nodes().Value(i); + len += pnt1.Distance(pnt2); + } -QuantityArea MeasureToolBRep::area(const GraphicsOwnerPtr& owner) const -{ - const TopoDS_Shape& shape = getShape(owner); - throwErrorIf(shape.IsNull() || shape.ShapeType() != TopAbs_FACE); - GProp_GProps gprops; - BRepGProp::SurfaceProperties(TopoDS::Face(shape), gprops); - const double area = gprops.Mass(); - return area * Quantity_SquareMillimeter; + return len * Quantity_Millimeter; + } } } // namespace Mayo diff --git a/src/app/measure_tool_brep.h b/src/measure/measure_tool_brep.h similarity index 67% rename from src/app/measure_tool_brep.h rename to src/measure/measure_tool_brep.h index 33dce1a1..dec02fdd 100644 --- a/src/app/measure_tool_brep.h +++ b/src/measure/measure_tool_brep.h @@ -8,6 +8,9 @@ #include "measure_tool.h" +class TopoDS_Edge; +class TopoDS_Shape; + namespace Mayo { // Provides measurement services for BRep shapes @@ -23,6 +26,16 @@ class MeasureToolBRep : public IMeasureTool { MeasureAngle angle(const GraphicsOwnerPtr& owner1, const GraphicsOwnerPtr& owner2) const override; QuantityLength length(const GraphicsOwnerPtr& owner) const override; QuantityArea area(const GraphicsOwnerPtr& owner) const override; + + static gp_Pnt brepVertexPosition(const TopoDS_Shape& shape); + static MeasureCircle brepCircle(const TopoDS_Shape& shape); + static MeasureMinDistance brepMinDistance(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2); + static MeasureAngle brepAngle(const TopoDS_Shape& shape1, const TopoDS_Shape& shape2); + static QuantityLength brepLength(const TopoDS_Shape& shape); + +private: + static MeasureCircle brepCircleFromGeometricEdge(const TopoDS_Edge& edge); + static MeasureCircle brepCircleFromPolygonEdge(const TopoDS_Edge& edge); }; } // namespace Mayo diff --git a/src/app/measure_type.h b/src/measure/measure_type.h similarity index 100% rename from src/app/measure_type.h rename to src/measure/measure_type.h diff --git a/tests/inputs/cube.bin b/tests/inputs/cube.bin new file mode 100644 index 00000000..3494f586 Binary files /dev/null and b/tests/inputs/cube.bin differ diff --git a/tests/inputs/cube.gltf b/tests/inputs/cube.gltf new file mode 100644 index 00000000..c437c550 --- /dev/null +++ b/tests/inputs/cube.gltf @@ -0,0 +1 @@ +{"accessors":[{"bufferView":0,"byteOffset":0,"componentType":5126,"count":4,"max":[0.0,0.01,0.0],"min":[0.0,0.0,-0.01],"type":"VEC3"},{"bufferView":0,"byteOffset":48,"componentType":5126,"count":4,"max":[0.01,0.01,0.0],"min":[0.01,0.0,-0.01],"type":"VEC3"},{"bufferView":0,"byteOffset":96,"componentType":5126,"count":4,"max":[0.01,0.01,0.0],"min":[0.0,0.0,0.0],"type":"VEC3"},{"bufferView":0,"byteOffset":144,"componentType":5126,"count":4,"max":[0.01,0.01,-0.01],"min":[0.0,0.0,-0.01],"type":"VEC3"},{"bufferView":0,"byteOffset":192,"componentType":5126,"count":4,"max":[0.01,0.0,0.0],"min":[0.0,0.0,-0.01],"type":"VEC3"},{"bufferView":0,"byteOffset":240,"componentType":5126,"count":4,"max":[0.01,0.01,0.0],"min":[0.0,0.01,-0.01],"type":"VEC3"},{"bufferView":1,"byteOffset":0,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":48,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":96,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":144,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":192,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":1,"byteOffset":240,"componentType":5126,"count":4,"type":"VEC3"},{"bufferView":2,"byteOffset":0,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":12,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":24,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":36,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":48,"componentType":5123,"count":6,"type":"SCALAR"},{"bufferView":2,"byteOffset":60,"componentType":5123,"count":6,"type":"SCALAR"}],"asset":{"generator":"Open CASCADE Technology 7.6 [dev.opencascade.org]","version":"2.0","extras":{"Author":"Author","Originating system":"FreeCAD","Organization":"","Comments":"FreeCAD Model"}},"bufferViews":[{"buffer":0,"byteLength":288,"byteOffset":0,"byteStride":12,"target":34962},{"buffer":0,"byteLength":288,"byteOffset":288,"byteStride":12,"target":34962},{"buffer":0,"byteLength":72,"byteOffset":576,"target":34963}],"buffers":[{"byteLength":648,"uri":"cube.bin"}],"materials":[{"name":"mat_0","pbrMetallicRoughness":{"baseColorFactor":[0.6038273572921753,0.6038273572921753,0.6038273572921753,1.0],"roughnessFactor":0.2121320366859436},"doubleSided":true}],"meshes":[{"name":"Cube","primitives":[{"attributes":{"NORMAL":6,"POSITION":0},"indices":12,"material":0,"mode":4},{"attributes":{"NORMAL":7,"POSITION":1},"indices":13,"material":0,"mode":4},{"attributes":{"NORMAL":8,"POSITION":2},"indices":14,"material":0,"mode":4},{"attributes":{"NORMAL":9,"POSITION":3},"indices":15,"material":0,"mode":4},{"attributes":{"NORMAL":10,"POSITION":4},"indices":16,"material":0,"mode":4},{"attributes":{"NORMAL":11,"POSITION":5},"indices":17,"material":0,"mode":4}]}],"nodes":[{"mesh":0,"name":"Cube"}],"scene":0,"scenes":[{"nodes":[0]}]} \ No newline at end of file diff --git a/tests/inputs/cube.off b/tests/inputs/cube.off new file mode 100644 index 00000000..bb60e754 --- /dev/null +++ b/tests/inputs/cube.off @@ -0,0 +1,38 @@ +OFF +24 12 0 +0 0 0 0.603827 0.603827 0.603827 +0 0 10 0.603827 0.603827 0.603827 +0 10 0 0.603827 0.603827 0.603827 +0 10 10 0.603827 0.603827 0.603827 +10 0 0 0.603827 0.603827 0.603827 +10 0 10 0.603827 0.603827 0.603827 +10 10 0 0.603827 0.603827 0.603827 +10 10 10 0.603827 0.603827 0.603827 +0 0 0 0.603827 0.603827 0.603827 +10 0 0 0.603827 0.603827 0.603827 +0 0 10 0.603827 0.603827 0.603827 +10 0 10 0.603827 0.603827 0.603827 +0 10 0 0.603827 0.603827 0.603827 +10 10 0 0.603827 0.603827 0.603827 +0 10 10 0.603827 0.603827 0.603827 +10 10 10 0.603827 0.603827 0.603827 +0 0 0 0.603827 0.603827 0.603827 +0 10 0 0.603827 0.603827 0.603827 +10 0 0 0.603827 0.603827 0.603827 +10 10 0 0.603827 0.603827 0.603827 +0 0 10 0.603827 0.603827 0.603827 +0 10 10 0.603827 0.603827 0.603827 +10 0 10 0.603827 0.603827 0.603827 +10 10 10 0.603827 0.603827 0.603827 +3 1 0 2 +3 1 2 3 +3 5 4 6 +3 5 6 7 +3 11 9 8 +3 11 8 10 +3 15 13 12 +3 15 12 14 +3 19 17 16 +3 19 16 18 +3 23 21 20 +3 23 20 22 diff --git a/tests/runtests.cpp b/tests/runtests.cpp index 80d48223..c315fe41 100644 --- a/tests/runtests.cpp +++ b/tests/runtests.cpp @@ -5,6 +5,7 @@ ****************************************************************************/ #include "test_base.h" +#include "test_measure.h" #include "test_app.h" #include @@ -24,6 +25,7 @@ int runTests(int argc, char* argv[]) int retcode = 0; std::vector> vecTest; vecTest.emplace_back(new Mayo::TestBase); + vecTest.emplace_back(new Mayo::TestMeasure); vecTest.emplace_back(new Mayo::TestApp); for (const std::unique_ptr& test : vecTest) retcode += QTest::qExec(test.get(), args); diff --git a/tests/test_app.cpp b/tests/test_app.cpp index 79a8b2c2..e7c06331 100644 --- a/tests/test_app.cpp +++ b/tests/test_app.cpp @@ -5,7 +5,7 @@ ****************************************************************************/ // Avoid MSVC conflicts with M_E, M_LOG2, ... -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES) # define _USE_MATH_DEFINES #endif @@ -14,10 +14,10 @@ #include "../src/app/filepath_conv.h" #include "../src/app/qstring_conv.h" #include "../src/app/qstring_utils.h" +#include "../src/app/qtgui_utils.h" #include "../src/app/recent_files.h" #include "../src/app/theme.h" #include "../src/io_occ/io_occ.h" -#include "../src/gui/qtgui_utils.h" #include #include diff --git a/tests/test_base.cpp b/tests/test_base.cpp index 9783b439..7530422b 100644 --- a/tests/test_base.cpp +++ b/tests/test_base.cpp @@ -5,7 +5,7 @@ ****************************************************************************/ // Avoid MSVC conflicts with M_E, M_LOG2, ... -#ifdef _MSC_VER +#if defined(_MSC_VER) && !defined(_USE_MATH_DEFINES) # define _USE_MATH_DEFINES #endif @@ -33,7 +33,10 @@ #include "../src/base/tkernel_utils.h" #include "../src/base/unit.h" #include "../src/base/unit_system.h" +#include "../src/io_dxf/io_dxf.h" #include "../src/io_occ/io_occ.h" +#include "../src/io_ply/io_ply_reader.h" +#include "../src/io_ply/io_ply_writer.h" #include #include @@ -47,10 +50,10 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -58,7 +61,9 @@ #include #include #include +#include #include +#include #include Q_DECLARE_METATYPE(Mayo::UnitSystem::TranslateResult) @@ -74,15 +79,58 @@ Q_DECLARE_METATYPE(Mayo::PropertyValueConversion::Variant) namespace Mayo { // For the sake of QCOMPARE() -static bool operator==( - const UnitSystem::TranslateResult& lhs, - const UnitSystem::TranslateResult& rhs) +static bool operator==(const UnitSystem::TranslateResult& lhs, const UnitSystem::TranslateResult& rhs) { return std::abs(lhs.value - rhs.value) < 1e-6 && std::strcmp(lhs.strUnit, rhs.strUnit) == 0 && std::abs(lhs.factor - rhs.factor) < 1e-6; } +// Equivalent of QSignalSpy for KDBindings signals +struct SignalEmitSpy { + struct UnknownType {}; + using ArgValue = std::variant; + using SignalArguments = std::vector; + + template + SignalEmitSpy(Signal* signal) { + this->sigConnection = signal->connect([=](Args... args) { + ++this->count; + SignalArguments sigArgs; + SignalEmitSpy::recordArgs(&sigArgs, args...); + this->vecSignals.push_back(std::move(sigArgs)); + }); + } + + ~SignalEmitSpy() { + this->sigConnection.disconnect(); + } + + static void recordArgs(SignalArguments* /*ptr*/) { + } + + template + static void recordArgs(SignalArguments* ptr, Arg arg, Args... args) { + if constexpr (std::is_integral_v) { + if constexpr (std::is_signed_v) { + ptr->push_back(static_cast(arg)); + } + else { + ptr->push_back(static_cast(arg)); + } + } + else { + ptr->push_back(UnknownType{}); + } + + SignalEmitSpy::recordArgs(ptr, args...); + } + + int count = 0; + std::vector vecSignals; + SignalConnectionHandle sigConnection; +}; + void TestBase::Application_test() { auto app = Application::instance(); @@ -95,18 +143,18 @@ void TestBase::Application_test() QCOMPARE(app->documentCount(), 0); { // Add & remove a document - QSignalSpy sigSpy_documentAdded(app.get(), &Application::documentAdded); + SignalEmitSpy spyDocAdded(&app->signalDocumentAdded); DocumentPtr doc = app->newDocument(); QVERIFY(!doc.IsNull()); - QCOMPARE(sigSpy_documentAdded.count(), 1); + QCOMPARE(spyDocAdded.count, 1); QCOMPARE(app->documentCount(), 1); QCOMPARE(app->findIndexOfDocument(doc), 0); QCOMPARE(app->findDocumentByIndex(0).get(), doc.get()); QCOMPARE(app->findDocumentByIdentifier(doc->identifier()).get(), doc.get()); - QSignalSpy sigSpy_documentAboutToClose(app.get(), &Application::documentAboutToClose); + SignalEmitSpy spyDocClosed(&app->signalDocumentAboutToClose); app->closeDocument(doc); - QCOMPARE(sigSpy_documentAboutToClose.count(), 1); + QCOMPARE(spyDocClosed.count, 1); QCOMPARE(app->documentCount(), 0); } @@ -114,17 +162,17 @@ void TestBase::Application_test() DocumentPtr doc = app->newDocument(); auto _ = gsl::finally([=]{ app->closeDocument(doc); }); QCOMPARE(doc->entityCount(), 0); - QSignalSpy sigSpy_docEntityAdded(doc.get(), &Document::entityAdded); + SignalEmitSpy spyEntityAdded(&app->signalDocumentEntityAdded); const bool okImport = fnImportInDocument(doc, "tests/inputs/cube.step"); QVERIFY(okImport); - QCOMPARE(sigSpy_docEntityAdded.count(), 1); + QCOMPARE(spyEntityAdded.count, 1); QCOMPARE(doc->entityCount(), 1); QVERIFY(XCaf::isShape(doc->entityLabel(0))); QCOMPARE(CafUtils::labelAttrStdName(doc->entityLabel(0)), to_OccExtString("Cube")); - QSignalSpy sigSpy_docEntityAboutToBeDestroyed(doc.get(), &Document::entityAboutToBeDestroyed); + SignalEmitSpy spyEntityDestroyed(&app->signalDocumentEntityAboutToBeDestroyed); doc->destroyEntity(doc->entityTreeNodeId(0)); - QCOMPARE(sigSpy_docEntityAboutToBeDestroyed.count(), 1); + QCOMPARE(spyEntityDestroyed.count, 1); QCOMPARE(doc->entityCount(), 0); } @@ -153,6 +201,14 @@ void TestBase::Application_test() QCOMPARE(app->documentCount(), 0); } +void TestBase::DocumentRefCount_test() +{ + DocumentPtr doc = Application::instance()->newDocument(); + QVERIFY(doc->GetRefCount() > 1); + Application::instance()->closeDocument(doc); + QCOMPARE(doc->GetRefCount(), 1); +} + void TestBase::CppUtils_toggle_test() { bool v = false; @@ -187,7 +243,7 @@ void TestBase::TextId_test() void TestBase::FilePath_test() { const char strTestPath[] = "../as1-oc-214 - 測試文件.stp"; - const FilePath testPath = std::filesystem::u8path(strTestPath); + const FilePath testPath = std_filesystem::u8path(strTestPath); { const TCollection_AsciiString ascStrTestPath(strTestPath); @@ -281,8 +337,8 @@ void TestBase::PropertyQuantityValueConversion_test_data() QTest::addColumn("variantTo"); QTest::newRow("Length(25mm)") << "PropertyLength" << Variant("25mm") << Variant("25mm"); QTest::newRow("Length(2m)") << "PropertyLength" << Variant("2m") << Variant("2000mm"); - QTest::newRow("Length(1.57079rad)") << "PropertyAngle" << Variant("1.57079rad") << Variant("1.57079rad"); - QTest::newRow("Length(90°)") << "PropertyAngle" << Variant("90°") << Variant("1.570796rad"); + QTest::newRow("Angle(1.57079rad)") << "PropertyAngle" << Variant("1.57079rad") << Variant("1.57079rad"); + QTest::newRow("Angle(90°)") << "PropertyAngle" << Variant("90°") << Variant("1.570796rad"); } void TestBase::IO_probeFormat_test() @@ -306,6 +362,7 @@ void TestBase::IO_probeFormat_test_data() QTest::newRow("cube.stlb") << "tests/inputs/cube.stlb" << IO::Format_STL; QTest::newRow("cube.obj") << "tests/inputs/cube.obj" << IO::Format_OBJ; QTest::newRow("cube.ply") << "tests/inputs/cube.ply" << IO::Format_PLY; + QTest::newRow("cube.off") << "tests/inputs/cube.off" << IO::Format_OFF; } void TestBase::IO_probeFormatDirect_test() @@ -344,6 +401,9 @@ void TestBase::IO_probeFormatDirect_test() fnSetProbeInput("tests/inputs/cube.ply"); QCOMPARE(IO::probeFormat_PLY(input), IO::Format_PLY); + + fnSetProbeInput("tests/inputs/cube.off"); + QCOMPARE(IO::probeFormat_OFF(input), IO::Format_OFF); } void TestBase::IO_OccStaticVariablesRollback_test() @@ -406,6 +466,119 @@ void TestBase::IO_OccStaticVariablesRollback_test_data() QTest::newRow("var_str2") << "mayo.test.variable_str2" << QVariant("foo") << QVariant("blah"); } +void TestBase::IO_bugGitHub166_test() +{ + QFETCH(QString, strInputFilePath); + QFETCH(QString, strOutputFilePath); + QFETCH(IO::Format, outputFormat); + + auto app = Application::instance(); + DocumentPtr doc = app->newDocument(); + const bool okImport = m_ioSystem->importInDocument() + .targetDocument(doc) + .withFilepath(strInputFilePath.toStdString()) + .execute(); + QVERIFY(okImport); + QVERIFY(doc->entityCount() > 0); + + const bool okExport = m_ioSystem->exportApplicationItems() + .targetFile(strOutputFilePath.toStdString()) + .targetFormat(outputFormat) + .withItem(doc) + .execute(); + QVERIFY(okExport); + app->closeDocument(doc); + + doc = app->newDocument(); + const bool okImportOutput = m_ioSystem->importInDocument() + .targetDocument(doc) + .withFilepath(strOutputFilePath.toStdString()) + .execute(); + QVERIFY(okImportOutput); + QVERIFY(doc->entityCount() > 0); +} + +void TestBase::IO_bugGitHub166_test_data() +{ + QTest::addColumn("strInputFilePath"); + QTest::addColumn("strOutputFilePath"); + QTest::addColumn("outputFormat"); + + QTest::newRow("PLY->STL") << "tests/inputs/cube.ply" << "tests/outputs/cube.stl" << IO::Format_STL; + QTest::newRow("STL->PLY") << "tests/inputs/cube.stla" << "tests/outputs/cube.ply" << IO::Format_PLY; + +#if OCC_VERSION_HEX >= 0x070400 + QTest::newRow("OBJ->PLY") << "tests/inputs/cube.obj" << "tests/outputs/cube.ply" << IO::Format_PLY; + QTest::newRow("OBJ->STL") << "tests/inputs/cube.obj" << "tests/outputs/cube.stl" << IO::Format_STL; + QTest::newRow("glTF->PLY") << "tests/inputs/cube.gltf" << "tests/outputs/cube.ply" << IO::Format_PLY; + QTest::newRow("glTF->STL") << "tests/inputs/cube.gltf" << "tests/outputs/cube.stl" << IO::Format_STL; +#endif + +#if OCC_VERSION_HEX >= 0x070600 + QTest::newRow("PLY->OBJ") << "tests/inputs/cube.ply" << "tests/outputs/cube.obj" << IO::Format_OBJ; + QTest::newRow("STL->OBJ") << "tests/inputs/cube.stla" << "tests/outputs/cube.obj" << IO::Format_OBJ; + QTest::newRow("glTF->OBJ") << "tests/inputs/cube.gltf" << "tests/outputs/cube.obj" << IO::Format_OBJ; + QTest::newRow("OBJ->glTF") << "tests/inputs/cube.obj" << "tests/outputs/cube.glTF" << IO::Format_GLTF; +#endif +} + +void TestBase::DoubleToString_test() +{ + auto fnGetLocale = [](const char* name) -> std::optional { + try { + return std::locale(name); + } catch (...) { + qWarning().noquote() << QString("Locale '%1' not available").arg(name); + } + + return {}; + }; + + // Tests with "fr_FR" locale which is likely to be Windows-1252 or ISO8859-1 on Unix + std::vector frLocaleNames = { "fr_FR.ISO8859-15", "fr_FR.ISO-8859-15" }; +#ifndef MAYO_OS_WINDOWS + // No native utf8 support on Windows(or requires Windows 10 november 2019 update) + frLocaleNames.push_back("fr_FR.utf8"); +#endif + frLocaleNames.push_back("fr_FR"); + + std::optional frLocale; + for (const char* localeName : frLocaleNames) { + if (!frLocale) + frLocale = fnGetLocale(localeName); + } + + if (frLocale) { + qInfo() << "frLocale:" << QString::fromStdString(frLocale->name()); + // 1258. + { + //QCOMPARE(QString::fromStdString(to_stdString(1258.).locale(locale)), QLocale("fr_FR").toString(1258.)); + // Note: on Windows the QLocale unicode thousand separator is different from what's returned + // by internal toUtf8String() + // to_stdString(): U+00A0(NO-BREAK SPACE) + // QLocale::toString(): U+202F(NARROW NO-BREAK SPACE) + // Caused by usage of ICU in Qt? + const QString str = QString::fromStdString(to_stdString(1258.).locale(frLocale.value())); + QCOMPARE(str.at(0), '1'); + QVERIFY(str.at(1).isSpace()); + QCOMPARE(str.right(3), "258"); + } + + // 57.89 + { + QCOMPARE(to_stdString(57.89).locale(frLocale.value()).get(), "57,89"); + } + } + + // Tests with "C" locale + const std::locale& cLocale = std::locale::classic(); + QCOMPARE(to_stdString(0.5578).locale(cLocale).decimalCount(4).get(), "0.5578"); + QCOMPARE(to_stdString(0.5578).locale(cLocale).decimalCount(6).get(), "0.5578"); + QCOMPARE(to_stdString(0.5578).locale(cLocale).decimalCount(6).removeTrailingZeroes(false).get(), "0.557800"); + QCOMPARE(to_stdString(0.0).locale(cLocale).decimalCount(6).get(), "0"); + QCOMPARE(to_stdString(-45.6789).locale(cLocale).decimalCount(6).get(), "-45.6789"); +} + void TestBase::BRepUtils_test() { QVERIFY(BRepUtils::moreComplex(TopAbs_COMPOUND, TopAbs_SOLID)); @@ -719,6 +892,16 @@ void TestBase::UnitSystem_test_data() QTest::newRow("degrees(PIrad)") << UnitSystem::degrees(3.14159265358979323846 * Quantity_Radian) << UnitSystem::TranslateResult{ 180., "°", radDeg }; + + QTest::newRow("time(1s)") + << UnitSystem::milliseconds(1 * Quantity_Second) + << UnitSystem::TranslateResult{ 1000., "ms", Quantity_Millisecond.value() }; + QTest::newRow("time(5s)") + << UnitSystem::milliseconds(5 * Quantity_Second) + << UnitSystem::TranslateResult{ 5000., "ms", Quantity_Millisecond.value() }; + QTest::newRow("time(5s)") + << UnitSystem::milliseconds(5 * Quantity_Second) + << UnitSystem::TranslateResult{ 5000., "ms", Quantity_Millisecond.value() }; } void TestBase::LibTask_test() @@ -743,19 +926,19 @@ void TestBase::LibTask_test() } }); std::vector vecProgressRec; - QObject::connect( - &taskMgr, &TaskManager::progressChanged, - [&](TaskId taskId, int pct) { vecProgressRec.push_back({ taskId, pct }); }); + taskMgr.signalProgressChanged.connectSlot([&](TaskId taskId, int pct) { + vecProgressRec.push_back({ taskId, pct }); + }); - QSignalSpy sigSpy_started(&taskMgr, &TaskManager::started); - QSignalSpy sigSpy_ended(&taskMgr, &TaskManager::ended); + SignalEmitSpy sigStarted(&taskMgr.signalStarted); + SignalEmitSpy sigEnded(&taskMgr.signalEnded); taskMgr.run(taskId); taskMgr.waitForDone(taskId); - QCOMPARE(sigSpy_started.count(), 1); - QCOMPARE(sigSpy_ended.count(), 1); - QCOMPARE(qvariant_cast(sigSpy_started.front().at(0)), taskId); - QCOMPARE(qvariant_cast(sigSpy_ended.front().at(0)), taskId); + QCOMPARE(sigStarted.count, 1); + QCOMPARE(sigEnded.count, 1); + QCOMPARE(std::get(sigStarted.vecSignals.front().at(0)), taskId); + QCOMPARE(std::get(sigEnded.vecSignals.front().at(0)), taskId); QVERIFY(!vecProgressRec.empty()); int prevPct = 0; for (const ProgressRecord& rec : vecProgressRec) { @@ -824,6 +1007,9 @@ void TestBase::LibTree_test() void TestBase::initTestCase() { m_ioSystem = new IO::System; + m_ioSystem->addFactoryReader(std::make_unique()); + m_ioSystem->addFactoryReader(std::make_unique()); + m_ioSystem->addFactoryWriter(std::make_unique()); m_ioSystem->addFactoryReader(std::make_unique()); m_ioSystem->addFactoryWriter(std::make_unique()); IO::addPredefinedFormatProbes(m_ioSystem); diff --git a/tests/test_base.h b/tests/test_base.h index 79db1658..1d188b2e 100644 --- a/tests/test_base.h +++ b/tests/test_base.h @@ -17,6 +17,7 @@ class TestBase : public QObject { Q_OBJECT private slots: void Application_test(); + void DocumentRefCount_test(); void CppUtils_toggle_test(); void CppUtils_safeStaticCast_test(); @@ -35,6 +36,10 @@ private slots: void IO_probeFormatDirect_test(); void IO_OccStaticVariablesRollback_test(); void IO_OccStaticVariablesRollback_test_data(); + void IO_bugGitHub166_test(); + void IO_bugGitHub166_test_data(); + + void DoubleToString_test(); void BRepUtils_test(); diff --git a/tests/test_measure.cpp b/tests/test_measure.cpp new file mode 100644 index 00000000..ce835070 --- /dev/null +++ b/tests/test_measure.cpp @@ -0,0 +1,174 @@ +/**************************************************************************** +** Copyright (c) 2022, Fougue Ltd. +** All rights reserved. +** See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +****************************************************************************/ + +#include "test_measure.h" + +#include "../src/base/geom_utils.h" +#include "../src/base/unit_system.h" +#include "../src/measure/measure_tool_brep.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace Mayo { + +namespace { + +bool compareCircle(const gp_Circ& lhs, const gp_Circ& rhs, double tolerance = Precision::Confusion()) +{ + return lhs.Location().IsEqual(rhs.Location(), tolerance) + && lhs.Axis().Direction().IsEqual(rhs.Axis().Direction(), tolerance) + && std::abs(lhs.Radius() - rhs.Radius()) < tolerance; +} + +TopoDS_Edge makePolygonEdge(const TColgp_Array1OfPnt& points) +{ + TopoDS_Edge edge; + BRep_Builder builder; + builder.MakeEdge(edge, new Poly_Polygon3D(points)); + return edge; +} + +} // namespace + +void TestMeasure::BRepVertexPosition_test() +{ + const gp_Pnt pnt(154.5, 0.87, -487.64); + const TopoDS_Vertex vertex = BRepBuilderAPI_MakeVertex(pnt); + const gp_Pnt pntRes = MeasureToolBRep::brepVertexPosition(vertex); + QVERIFY(pntRes.IsEqual(pnt, Precision::Confusion())); +} + +void TestMeasure::BRepCircle_Regular_test() +{ + const gp_Pnt pntCenter{ 75.5, 0.8, 2548.16 }; + const gp_Dir dirNormal{ 1, 1, 1 }; + const double radius = 58.; + const GC_MakeCircle makeCircle(gp_Ax2(pntCenter, dirNormal), radius); + const TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(makeCircle.Value(), 0, 1.57); + const MeasureCircle circleRes = MeasureToolBRep::brepCircle(edge); + QVERIFY(circleRes.pntAnchor.IsEqual(GeomUtils::d0(BRepAdaptor_Curve(edge), 0), Precision::Confusion())); + QVERIFY(circleRes.isArc); + QVERIFY(compareCircle(circleRes.value, makeCircle.Value()->Circ())); +} + +void TestMeasure::BRepCircle_Ellipse_test() +{ + const gp_Pnt pntCenter{ -57.4, 4487.56, 1.8 }; + const gp_Dir dirNormal{ 1, 0, 1 }; + const double radius = 95.; + const GC_MakeEllipse makeEllipse(gp_Ax2(pntCenter, dirNormal), radius, radius); + const TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(makeEllipse.Value(), 0, 2.27); + const MeasureCircle circleRes = MeasureToolBRep::brepCircle(edge); + QVERIFY(circleRes.pntAnchor.IsEqual(GeomUtils::d0(BRepAdaptor_Curve(edge), 0), Precision::Confusion())); + QVERIFY(circleRes.isArc); + QVERIFY(compareCircle(circleRes.value, gp_Circ(gp_Ax2(pntCenter, dirNormal), radius))); +} + +void TestMeasure::BRepCircle_PseudoCircle_test() +{ + const gp_Pnt pntCenter{ 41.85, 1547.27, 45.89 }; + const gp_Dir dirNormal{ 0.5, 1.25, 0.8 }; + const double radius = 25.48; + const GC_MakeCircle makeCircle(gp_Ax2(pntCenter, dirNormal), radius); + GeomConvert_ApproxCurve approxCircle(makeCircle.Value(), Precision::Approximation(), GeomAbs_C1, 2048, 8); + QVERIFY(approxCircle.IsDone()); + QVERIFY(approxCircle.HasResult()); + const TopoDS_Edge edge = BRepBuilderAPI_MakeEdge(approxCircle.Curve(), 0, 2.98); + const MeasureCircle circleRes = MeasureToolBRep::brepCircle(edge); + QVERIFY(circleRes.pntAnchor.IsEqual(GeomUtils::d0(BRepAdaptor_Curve(edge), 0), Precision::Confusion())); + QVERIFY(circleRes.isArc); + QVERIFY(compareCircle(circleRes.value, makeCircle.Value()->Circ(), Precision::Approximation())); +} + +void TestMeasure::BRepCircle_PolygonEdge_test() +{ + const gp_Pnt pntCenter{ 41.85, 1547.27, 45.89 }; + const double radius = 25.48; + const int pntCount = 128; + TColgp_Array1OfPnt points(1, pntCount); + for (int i = 0; i < pntCount; ++i) { + const double pi = 3.14159265358979323846; + const double a = 1.5 * pi * (static_cast(i) / static_cast(pntCount)); + const double x = radius * std::cos(a); + const double y = radius * std::sin(a); + points.ChangeValue(i + 1) = gp_Pnt{ pntCenter.X() + x, pntCenter.Y() + y, pntCenter.Z() }; + } + + const TopoDS_Edge edge = makePolygonEdge(points); + const MeasureCircle circle = MeasureToolBRep::brepCircle(edge); + const GC_MakeCircle makeGeomCircle(gp_Ax2(pntCenter, gp::DZ()), radius); + QVERIFY(compareCircle(circle.value, makeGeomCircle.Value()->Circ())); + QVERIFY(circle.isArc); +} + +void TestMeasure::BRepMinDistance_TwoPoints_test() +{ + const gp_Pnt pnt1{ 41.85, 1547.27, 45.89 }; + const gp_Pnt pnt2{ -57.4, 4487.56, 1.8 }; + const TopoDS_Shape shape1 = BRepBuilderAPI_MakeVertex(pnt1); + const TopoDS_Shape shape2 = BRepBuilderAPI_MakeVertex(pnt2); + const MeasureMinDistance minDist = MeasureToolBRep::brepMinDistance(shape1, shape2); + QVERIFY(minDist.pnt1.IsEqual(pnt1, Precision::Confusion())); + QVERIFY(minDist.pnt2.IsEqual(pnt2, Precision::Confusion())); + QCOMPARE(UnitSystem::millimeters(minDist.value).value, pnt1.Distance(pnt2)); +} + +void TestMeasure::BRepMinDistance_TwoBoxes_test() +{ + const gp_Pnt box1_min{ 5, 5, 5 }; + const gp_Pnt box1_max{ 20, 7, 7 }; + const gp_Pnt box2_min{ 40, 5, 5 }; + const gp_Pnt box2_max{ 55, 7, 7 }; + const TopoDS_Shape shape1 = BRepPrimAPI_MakeBox(box1_min, box1_max); + const TopoDS_Shape shape2 = BRepPrimAPI_MakeBox(box2_min, box2_max); + const MeasureMinDistance minDist = MeasureToolBRep::brepMinDistance(shape1, shape2); + QCOMPARE(UnitSystem::millimeters(minDist.value).value, std::abs(box1_max.X() - box2_min.X())); + QCOMPARE(UnitSystem::millimeters(minDist.value).value, minDist.pnt1.Distance(minDist.pnt2)); +} + +void TestMeasure::BRepAngle_TwoLinesIntersect_test() +{ + const TopoDS_Shape shape1 = BRepBuilderAPI_MakeEdge(gp_Lin(gp::Origin(), gp::DX())); + const TopoDS_Shape shape2 = BRepBuilderAPI_MakeEdge(gp_Lin(gp::Origin(), gp::DY())); + const MeasureAngle angle = MeasureToolBRep::brepAngle(shape1, shape2); + QVERIFY(angle.pntCenter.IsEqual(gp::Origin(), Precision::Confusion())); + QCOMPARE(angle.value, 90. * Quantity_Degree); +} + +void TestMeasure::BRepAngle_TwoLinesParallelError_test() +{ + const TopoDS_Shape shape1 = BRepBuilderAPI_MakeEdge(gp_Lin(gp::Origin(), gp::DX())); + const TopoDS_Shape shape2 = BRepBuilderAPI_MakeEdge(gp_Lin({ 0, 5, 5 }, gp::DX())); + QVERIFY_EXCEPTION_THROWN(MeasureToolBRep::brepAngle(shape1, shape2), IMeasureError); +} + +void TestMeasure::BRepLength_PolygonEdge_test() +{ + TColgp_Array1OfPnt points(1, 5); + points.ChangeValue(1) = gp::Origin(); + points.ChangeValue(2) = gp_Pnt{0, 10, 0}; + points.ChangeValue(3) = gp_Pnt{0, 10, 5}; + points.ChangeValue(4) = gp_Pnt{7, 10, 5}; + points.ChangeValue(5) = gp_Pnt{7, 12, 5}; + const TopoDS_Edge edge = makePolygonEdge(points); + const QuantityLength len = MeasureToolBRep::brepLength(edge); + QCOMPARE(UnitSystem::millimeters(len).value, 24.); +} + +} // namespace Mayo diff --git a/tests/test_measure.h b/tests/test_measure.h new file mode 100644 index 00000000..8950ddd8 --- /dev/null +++ b/tests/test_measure.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 +#include + +namespace Mayo { + +class TestMeasure : public QObject { + Q_OBJECT +private slots: + void BRepVertexPosition_test(); + + void BRepCircle_Regular_test(); + void BRepCircle_Ellipse_test(); + void BRepCircle_PseudoCircle_test(); + void BRepCircle_PolygonEdge_test(); + + void BRepMinDistance_TwoPoints_test(); + void BRepMinDistance_TwoBoxes_test(); + + void BRepAngle_TwoLinesIntersect_test(); + void BRepAngle_TwoLinesParallelError_test(); + + void BRepLength_PolygonEdge_test(); +}; + +} // namespace Mayo diff --git a/tests/tests.pri b/tests/tests.pri index 262932c8..87401ef9 100644 --- a/tests/tests.pri +++ b/tests/tests.pri @@ -1,16 +1,26 @@ +#**************************************************************************** +#* Copyright (c) 2022, Fougue Ltd. +#* All rights reserved. +#* See license at https://github.com/fougue/mayo/blob/master/LICENSE.txt +#**************************************************************************** + QT += testlib HEADERS += \ $$PWD/test_app.h \ $$PWD/test_base.h \ + $$PWD/test_measure.h \ SOURCES += \ $$PWD/runtests.cpp \ $$PWD/test_app.cpp \ $$PWD/test_base.cpp \ + $$PWD/test_measure.cpp \ # Copy input files CONFIG += file_copies COPIES += MayoTestsInputs MayoTestsInputs.files = $$files($$PWD/inputs/*.*) MayoTestsInputs.path = $$OUT_PWD/tests/inputs + +mkpath($$OUT_PWD/tests/outputs) diff --git a/version.pri b/version.pri index 26c185c4..79555ca8 100644 --- a/version.pri +++ b/version.pri @@ -9,7 +9,7 @@ defined(HAVE_GIT, var) { } MAYO_VERSION_MAJ = 0 -MAYO_VERSION_MIN = 6 +MAYO_VERSION_MIN = 7 MAYO_VERSION_PAT = 0 VERSION = $${MAYO_VERSION_MAJ}.$${MAYO_VERSION_MIN}.$${MAYO_VERSION_PAT}.$${MAYO_VERSION_REVNUM} MAYO_VERSION = $${VERSION}-$$MAYO_VERSION_COMMIT