Add main menu.
- Add about window, - Add acknowlegements window, - Add multiple documents support.master
parent
f47148a537
commit
38fb0c022a
|
@ -0,0 +1,402 @@
|
||||||
|
<h1>Acknowledgements</h1>
|
||||||
|
<p>
|
||||||
|
Portions of this Software(Dust3D) may utilize the following copyrighted material, the use of which is hereby acknowledged.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h1>QtAwesome</h1>
|
||||||
|
<pre>
|
||||||
|
MIT License
|
||||||
|
===========
|
||||||
|
|
||||||
|
Copyright 2013-2015 [Reliable Bits Software by Blommers IT](http://blommersit.nl). All Rights Reserved.
|
||||||
|
Author [Rick Blommers](mailto:rick@blommersit.nl)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
|
||||||
|
Font Awesome License
|
||||||
|
====================
|
||||||
|
|
||||||
|
[https://github.com/FortAwesome/Font-Awesome](https://github.com/FortAwesome/Font-Awesome)
|
||||||
|
|
||||||
|
The Font Awesome font is licensed under the SIL Open Font License - [http://scripts.sil.org/OFL](http://scripts.sil.org/OFL)
|
||||||
|
The Font Awesome pictograms are licensed under the CC BY 3.0 License - [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/)
|
||||||
|
"Font Awesome by Dave Gandy - http://fortawesome.github.com/Font-Awesome"
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>CGAL</h1>
|
||||||
|
<pre>
|
||||||
|
LICENSE
|
||||||
|
----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
The CGAL software consists of several parts, each of which is licensed under
|
||||||
|
an open source license. It is also possible to obtain commercial licenses
|
||||||
|
from GeometryFactory (www.geometryfactory.com) for all or parts of CGAL.
|
||||||
|
|
||||||
|
The source code of the CGAL library can be found in the directories
|
||||||
|
"src/CGAL", "src/CGALQt", "src/CGALQt5" and "include/CGAL" (with the
|
||||||
|
exception of "include/CGAL/CORE", "include/CGAL/OpenNL").
|
||||||
|
It is specified in each file of the CGAL library which
|
||||||
|
license applies to it. This is either the GNU General Public License
|
||||||
|
or the GNU Lesser General Public License (as published by the Free Software
|
||||||
|
Foundation; either version 3 of the License or (at your option) any later
|
||||||
|
version). The texts of both licenses can be found in the files LICENSE.GPL
|
||||||
|
and LICENSE.LGPL.
|
||||||
|
|
||||||
|
The following files are modified versions taken from Boost and are licensed
|
||||||
|
under the Boost Software License (see LICENSE.BSL).
|
||||||
|
- include/CGAL/auto_link/auto_link.h
|
||||||
|
- include/CGAL/internal/container_fwd_fixed.hpp
|
||||||
|
- include/CGAL/internal/boost/array_binary_tree.hpp
|
||||||
|
- include/CGAL/internal/boost/mutable_heap.hpp
|
||||||
|
- include/CGAL/internal/boost/mutable_queue.hpp
|
||||||
|
|
||||||
|
Distributed along with CGAL (for the users' convenience), but not part of
|
||||||
|
CGAL, are the following third-party libraries, available under their own
|
||||||
|
licenses:
|
||||||
|
|
||||||
|
- CORE, in the directories "include/CGAL/CORE" and "src/CGAL_Core", is
|
||||||
|
licensed under the LGPL (see LICENSE.LGPL).
|
||||||
|
- ImageIO, in the directory "src/CGAL_ImageIO", is licensed under the LGPL
|
||||||
|
(see LICENSE.LGPL).
|
||||||
|
- OpenNL, in the directory "include/CGAL/OpenNL", is licensed under the LGPL
|
||||||
|
(see LICENSE.LGPL).
|
||||||
|
|
||||||
|
All other files that do not have an explicit copyright notice (e.g., all
|
||||||
|
examples and some demos) are licensed under a very permissive license. The
|
||||||
|
exact license text can be found in the file LICENSE.FREE_USE.
|
||||||
|
|
||||||
|
More information on the CGAL license can be found at
|
||||||
|
https://www.cgal.org/license.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>cgmath</h1>
|
||||||
|
<pre>
|
||||||
|
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
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright [yyyy] [name of copyright owner]
|
||||||
|
|
||||||
|
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.
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>petgraph</h1>
|
||||||
|
<pre>
|
||||||
|
Copyright (c) 2015
|
||||||
|
|
||||||
|
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.
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>meshlite</h1>
|
||||||
|
<pre>
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018 XingYi Hu
|
||||||
|
|
||||||
|
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.
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Reza Nourai</h1>
|
||||||
|
<pre>
|
||||||
|
https://blogs.msdn.microsoft.com/rezanour/2011/08/07/barycentric-coordinates-and-point-in-triangle-tests/
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Cyranose</h1>
|
||||||
|
<pre>
|
||||||
|
https://www.opengl.org/discussion_boards/showthread.php/159385-Deriving-angles-from-0-to-360-from-Dot-Product
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Dan Sunday</h1>
|
||||||
|
<pre>
|
||||||
|
http://geomalgorithms.com/index.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>user3146587</h1>
|
||||||
|
<pre>
|
||||||
|
https://stackoverflow.com/questions/21114796/3d-ray-quad-intersection-test-in-java
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Qt</h1>
|
||||||
|
<pre>
|
||||||
|
https://www.qt.io/
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Qt Dark Theme</h1>
|
||||||
|
<pre>
|
||||||
|
https://gist.github.com/QuantumCD/6245215
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>A Browser for QDebug Log Output</h1>
|
||||||
|
<pre>
|
||||||
|
https://wiki.qt.io/Browser_for_QDebug_output
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Hello GL2 Example</h1>
|
||||||
|
<pre>
|
||||||
|
http://doc.qt.io/qt-5/qtopengl-hellogl2-example.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Jimmy Gunawan</h1>
|
||||||
|
<pre>
|
||||||
|
INSPIRATION / Pixar Monster Factory Part One
|
||||||
|
http://blendersushi.blogspot.com.au/2013/06/inspiration-pixar-monster-factory-part.html
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Zhongping Ji, Ligang Liu, Yigang Wang</h1>
|
||||||
|
<pre>
|
||||||
|
B-Mesh: A Fast Modeling System for Base Meshes of 3D Articulated Shapes
|
||||||
|
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.357.7134&rep=rep1&type=pdf
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>danielhst</h1>
|
||||||
|
<pre>
|
||||||
|
https://github.com/danielhst/3d-Hull-gift-wrap/blob/master/giftWrap.lua
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<h1>Catmull–Clark subdivision surface/C</h1>
|
||||||
|
<pre>
|
||||||
|
https://rosettacode.org/wiki/Catmull%E2%80%93Clark_subdivision_surface/C
|
||||||
|
</pre>
|
17
README.md
17
README.md
|
@ -3,22 +3,5 @@ WIP...
|
||||||
Build
|
Build
|
||||||
--------
|
--------
|
||||||
```
|
```
|
||||||
$ cd thirdparty/carve-1.4.0/build
|
|
||||||
$ cmake ../
|
|
||||||
$ make && make install
|
|
||||||
|
|
||||||
$ qmake -spec macx-xcode
|
$ qmake -spec macx-xcode
|
||||||
```
|
```
|
||||||
|
|
||||||
UI Resources Source
|
|
||||||
--------
|
|
||||||
Qt Dark Theme:
|
|
||||||
- https://gist.github.com/QuantumCD/6245215
|
|
||||||
|
|
||||||
Icons:
|
|
||||||
- https://material.io/icons/
|
|
||||||
18dp white
|
|
||||||
3d rotation -> rotate
|
|
||||||
add circle outline -> add
|
|
||||||
zoom in -> zoomin
|
|
||||||
zoom out -> zoomout
|
|
||||||
|
|
18
dust3d.pro
18
dust3d.pro
|
@ -2,6 +2,21 @@ QT += core widgets opengl
|
||||||
CONFIG += debug
|
CONFIG += debug
|
||||||
RESOURCES += resources.qrc
|
RESOURCES += resources.qrc
|
||||||
|
|
||||||
|
HUMAN_VERSION = "0.0-alpha1"
|
||||||
|
REPOSITORY_URL = "https://github.com/huxingyi/dust3d"
|
||||||
|
ISSUES_URL = "https://github.com/huxingyi/dust3d/issues"
|
||||||
|
VERSION = 0.0.0.1
|
||||||
|
QMAKE_TARGET_COMPANY = Dust3D
|
||||||
|
QMAKE_TARGET_PRODUCT = Dust3D
|
||||||
|
QMAKE_TARGET_DESCRIPTION = "Aim to be a quick modeling tool for game development"
|
||||||
|
QMAKE_TARGET_COPYRIGHT = "Copyright (C) 2018 Dust3D Project. All Rights Reserved."
|
||||||
|
|
||||||
|
DEFINES += "APP_NAME=\"\\\"$$QMAKE_TARGET_PRODUCT\\\"\""
|
||||||
|
DEFINES += "APP_VER=\"\\\"$$VERSION\\\"\""
|
||||||
|
DEFINES += "APP_HUMAN_VER=\"\\\"$$HUMAN_VERSION\\\"\""
|
||||||
|
DEFINES += "APP_REPOSITORY_URL=\"\\\"$$REPOSITORY_URL\\\"\""
|
||||||
|
DEFINES += "APP_ISSUES_URL=\"\\\"$$ISSUES_URL\\\"\""
|
||||||
|
|
||||||
include(thirdparty/QtAwesome/QtAwesome/QtAwesome.pri)
|
include(thirdparty/QtAwesome/QtAwesome/QtAwesome.pri)
|
||||||
|
|
||||||
INCLUDEPATH += src
|
INCLUDEPATH += src
|
||||||
|
@ -30,6 +45,9 @@ HEADERS += src/skeletongraphicswidget.h
|
||||||
SOURCES += src/skeletonpartlistwidget.cpp
|
SOURCES += src/skeletonpartlistwidget.cpp
|
||||||
HEADERS += src/skeletonpartlistwidget.h
|
HEADERS += src/skeletonpartlistwidget.h
|
||||||
|
|
||||||
|
SOURCES += src/aboutwidget.cpp
|
||||||
|
HEADERS += src/aboutwidget.h
|
||||||
|
|
||||||
SOURCES += src/meshgenerator.cpp
|
SOURCES += src/meshgenerator.cpp
|
||||||
HEADERS += src/meshgenerator.h
|
HEADERS += src/meshgenerator.h
|
||||||
|
|
||||||
|
|
Binary file not shown.
|
@ -1,5 +1,6 @@
|
||||||
<!DOCTYPE RCC><RCC version="1.0">
|
<!DOCTYPE RCC><RCC version="1.0">
|
||||||
<qresource>
|
<qresource>
|
||||||
<file>resources/dust3d_jezzasoft.png</file>
|
<file>resources/dust3d_vertical.png</file>
|
||||||
|
<file>ACKNOWLEDGEMENTS.html</file>
|
||||||
</qresource>
|
</qresource>
|
||||||
</RCC>
|
</RCC>
|
Binary file not shown.
Before Width: | Height: | Size: 2.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
#include <QTextEdit>
|
||||||
|
#include <QVBoxLayout>
|
||||||
|
#include "aboutwidget.h"
|
||||||
|
|
||||||
|
AboutWidget::AboutWidget()
|
||||||
|
{
|
||||||
|
QTextEdit *versionInfoLabel = new QTextEdit;
|
||||||
|
versionInfoLabel->setText(QString("%1 %2 (version: %3 build: %4 %5)").arg(APP_NAME).arg(APP_HUMAN_VER).arg(APP_VER).arg(__DATE__).arg(__TIME__));
|
||||||
|
versionInfoLabel->setReadOnly(true);
|
||||||
|
|
||||||
|
QVBoxLayout *mainLayout = new QVBoxLayout;
|
||||||
|
mainLayout->addWidget(versionInfoLabel);
|
||||||
|
|
||||||
|
setLayout(mainLayout);
|
||||||
|
setFixedSize(QSize(350, 75));
|
||||||
|
|
||||||
|
setWindowTitle(APP_NAME);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
#ifndef ABOUT_WIDGET_H
|
||||||
|
#define ABOUT_WIDGET_H
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
class AboutWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
AboutWidget();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
|
@ -9,7 +9,6 @@ LogBrowser::LogBrowser(QObject *parent) :
|
||||||
qRegisterMetaType<QtMsgType>("QtMsgType");
|
qRegisterMetaType<QtMsgType>("QtMsgType");
|
||||||
m_browserDialog = new LogBrowserDialog;
|
m_browserDialog = new LogBrowserDialog;
|
||||||
connect(this, SIGNAL(sendMessage(QtMsgType,QString)), m_browserDialog, SLOT(outputMessage(QtMsgType,QString)), Qt::QueuedConnection);
|
connect(this, SIGNAL(sendMessage(QtMsgType,QString)), m_browserDialog, SLOT(outputMessage(QtMsgType,QString)), Qt::QueuedConnection);
|
||||||
m_browserDialog->show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
LogBrowser::~LogBrowser()
|
LogBrowser::~LogBrowser()
|
||||||
|
@ -17,6 +16,23 @@ LogBrowser::~LogBrowser()
|
||||||
delete m_browserDialog;
|
delete m_browserDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LogBrowser::showDialog()
|
||||||
|
{
|
||||||
|
m_browserDialog->show();
|
||||||
|
m_browserDialog->activateWindow();
|
||||||
|
m_browserDialog->raise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LogBrowser::hideDialog()
|
||||||
|
{
|
||||||
|
m_browserDialog->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LogBrowser::isDialogVisible()
|
||||||
|
{
|
||||||
|
return m_browserDialog->isVisible();
|
||||||
|
}
|
||||||
|
|
||||||
void LogBrowser::outputMessage(QtMsgType type, const QString &msg)
|
void LogBrowser::outputMessage(QtMsgType type, const QString &msg)
|
||||||
{
|
{
|
||||||
printf("%s\n", msg.toUtf8().constData());
|
printf("%s\n", msg.toUtf8().constData());
|
||||||
|
|
|
@ -14,6 +14,9 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void outputMessage(QtMsgType type, const QString &msg);
|
void outputMessage(QtMsgType type, const QString &msg);
|
||||||
|
void showDialog();
|
||||||
|
void hideDialog();
|
||||||
|
bool isDialogVisible();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void sendMessage(QtMsgType type, const QString &msg);
|
void sendMessage(QtMsgType type, const QString &msg);
|
||||||
|
|
|
@ -38,6 +38,8 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
||||||
connect(m_saveButton, SIGNAL(clicked()), this, SLOT(save()));
|
connect(m_saveButton, SIGNAL(clicked()), this, SLOT(save()));
|
||||||
|
|
||||||
resize(400, 300);
|
resize(400, 300);
|
||||||
|
|
||||||
|
hide();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -100,15 +102,8 @@ void LogBrowserDialog::save()
|
||||||
|
|
||||||
void LogBrowserDialog::closeEvent(QCloseEvent *e)
|
void LogBrowserDialog::closeEvent(QCloseEvent *e)
|
||||||
{
|
{
|
||||||
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
e->ignore();
|
||||||
tr("Close Log Browser?"),
|
hide();
|
||||||
tr("Do you really want to close the log browser?"),
|
|
||||||
QMessageBox::Yes | QMessageBox::No);
|
|
||||||
|
|
||||||
if (answer == QMessageBox::Yes)
|
|
||||||
e->accept();
|
|
||||||
else
|
|
||||||
e->ignore();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LogBrowserDialog::keyPressEvent(QKeyEvent *e)
|
void LogBrowserDialog::keyPressEvent(QKeyEvent *e)
|
||||||
|
|
15
src/main.cpp
15
src/main.cpp
|
@ -2,21 +2,11 @@
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
#include <QStyleFactory>
|
#include <QStyleFactory>
|
||||||
#include <QFontDatabase>
|
#include <QFontDatabase>
|
||||||
#include <QPointer>
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QtGlobal>
|
#include <QtGlobal>
|
||||||
#include "logbrowser.h"
|
|
||||||
#include "skeletondocumentwindow.h"
|
#include "skeletondocumentwindow.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
|
||||||
QPointer<LogBrowser> g_logBrowser;
|
|
||||||
|
|
||||||
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
|
||||||
{
|
|
||||||
if (g_logBrowser)
|
|
||||||
g_logBrowser->outputMessage(type, msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
int main(int argc, char ** argv)
|
int main(int argc, char ** argv)
|
||||||
{
|
{
|
||||||
QApplication app(argc, argv);
|
QApplication app(argc, argv);
|
||||||
|
@ -50,10 +40,7 @@ int main(int argc, char ** argv)
|
||||||
font.setBold(false);
|
font.setBold(false);
|
||||||
QApplication::setFont(font);
|
QApplication::setFont(font);
|
||||||
|
|
||||||
g_logBrowser = new LogBrowser;
|
SkeletonDocumentWindow::createDocumentWindow();
|
||||||
qInstallMessageHandler(&outputMessage);
|
|
||||||
|
|
||||||
SkeletonDocumentWindow mainWindow;
|
|
||||||
mainWindow.showMaximized();
|
|
||||||
return app.exec();
|
return app.exec();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,21 @@ ModelWidget::ModelWidget(QWidget *parent)
|
||||||
setMouseTracking(true);
|
setMouseTracking(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ModelWidget::xRot()
|
||||||
|
{
|
||||||
|
return m_xRot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ModelWidget::yRot()
|
||||||
|
{
|
||||||
|
return m_yRot;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ModelWidget::zRot()
|
||||||
|
{
|
||||||
|
return m_zRot;
|
||||||
|
}
|
||||||
|
|
||||||
void ModelWidget::setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions)
|
void ModelWidget::setGraphicsFunctions(SkeletonGraphicsFunctions *graphicsFunctions)
|
||||||
{
|
{
|
||||||
m_graphicsFunctions = graphicsFunctions;
|
m_graphicsFunctions = graphicsFunctions;
|
||||||
|
|
|
@ -47,6 +47,12 @@ protected:
|
||||||
void mouseMoveEvent(QMouseEvent *event) override;
|
void mouseMoveEvent(QMouseEvent *event) override;
|
||||||
void wheelEvent(QWheelEvent *event) override;
|
void wheelEvent(QWheelEvent *event) override;
|
||||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||||
|
|
||||||
|
public:
|
||||||
|
int xRot();
|
||||||
|
int yRot();
|
||||||
|
int zRot();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int m_xRot;
|
int m_xRot;
|
||||||
int m_yRot;
|
int m_yRot;
|
||||||
|
|
|
@ -26,6 +26,7 @@ SkeletonDocument::~SkeletonDocument()
|
||||||
|
|
||||||
void SkeletonDocument::uiReady()
|
void SkeletonDocument::uiReady()
|
||||||
{
|
{
|
||||||
|
qDebug() << "uiReady";
|
||||||
emit editModeChanged();
|
emit editModeChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,24 +616,19 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot)
|
||||||
emit skeletonChanged();
|
emit skeletonChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot)
|
void SkeletonDocument::reset()
|
||||||
{
|
{
|
||||||
for (const auto &nodeIt: nodeMap) {
|
|
||||||
emit nodeRemoved(nodeIt.first);
|
|
||||||
}
|
|
||||||
for (const auto &edgeIt: edgeMap) {
|
|
||||||
emit edgeRemoved(edgeIt.first);
|
|
||||||
}
|
|
||||||
for (const auto &partIt : partMap) {
|
|
||||||
emit partRemoved(partIt.first);
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeMap.clear();
|
nodeMap.clear();
|
||||||
edgeMap.clear();
|
edgeMap.clear();
|
||||||
partMap.clear();
|
partMap.clear();
|
||||||
partIds.clear();
|
partIds.clear();
|
||||||
emit partListChanged();
|
emit cleanup();
|
||||||
|
emit skeletonChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot)
|
||||||
|
{
|
||||||
|
reset();
|
||||||
addFromSnapshot(snapshot);
|
addFromSnapshot(snapshot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -123,6 +123,7 @@ signals:
|
||||||
void partLockStateChanged(QUuid partId);
|
void partLockStateChanged(QUuid partId);
|
||||||
void partVisibleStateChanged(QUuid partId);
|
void partVisibleStateChanged(QUuid partId);
|
||||||
void partSubdivStateChanged(QUuid partId);
|
void partSubdivStateChanged(QUuid partId);
|
||||||
|
void cleanup();
|
||||||
public:
|
public:
|
||||||
SkeletonDocument();
|
SkeletonDocument();
|
||||||
~SkeletonDocument();
|
~SkeletonDocument();
|
||||||
|
@ -168,6 +169,7 @@ public slots:
|
||||||
void paste();
|
void paste();
|
||||||
void batchChangeBegin();
|
void batchChangeBegin();
|
||||||
void batchChangeEnd();
|
void batchChangeEnd();
|
||||||
|
void reset();
|
||||||
private:
|
private:
|
||||||
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
|
void splitPartByNode(std::vector<std::vector<QUuid>> *groups, QUuid nodeId);
|
||||||
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
|
void joinNodeAndNeiborsToGroup(std::vector<QUuid> *group, QUuid nodeId, std::set<QUuid> *visitMap, QUuid noUseEdgeId=QUuid());
|
||||||
|
|
|
@ -6,15 +6,74 @@
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QFileDialog>
|
#include <QFileDialog>
|
||||||
#include <QTabWidget>
|
#include <QTabWidget>
|
||||||
|
#include <QBuffer>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QMenuBar>
|
||||||
|
#include <QPointer>
|
||||||
|
#include <QApplication>
|
||||||
|
#include <set>
|
||||||
|
#include <QDesktopServices>
|
||||||
#include "skeletondocumentwindow.h"
|
#include "skeletondocumentwindow.h"
|
||||||
#include "skeletongraphicswidget.h"
|
#include "skeletongraphicswidget.h"
|
||||||
#include "skeletonpartlistwidget.h"
|
#include "skeletonpartlistwidget.h"
|
||||||
#include "theme.h"
|
#include "theme.h"
|
||||||
|
#include "ds3file.h"
|
||||||
|
#include "skeletonsnapshot.h"
|
||||||
|
#include "skeletonxml.h"
|
||||||
|
#include "logbrowser.h"
|
||||||
|
#include "util.h"
|
||||||
|
#include "aboutwidget.h"
|
||||||
|
|
||||||
|
QPointer<LogBrowser> g_logBrowser;
|
||||||
|
std::set<SkeletonDocumentWindow *> g_documentWindows;
|
||||||
|
QTextBrowser *g_acknowlegementsWidget = nullptr;
|
||||||
|
AboutWidget *g_aboutWidget = nullptr;
|
||||||
|
|
||||||
|
void outputMessage(QtMsgType type, const QMessageLogContext &context, const QString &msg)
|
||||||
|
{
|
||||||
|
if (g_logBrowser)
|
||||||
|
g_logBrowser->outputMessage(type, msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::SkeletonDocumentWindow::showAcknowlegements()
|
||||||
|
{
|
||||||
|
if (!g_acknowlegementsWidget) {
|
||||||
|
g_acknowlegementsWidget = new QTextBrowser;
|
||||||
|
g_acknowlegementsWidget->setWindowTitle(APP_NAME);
|
||||||
|
g_acknowlegementsWidget->setMinimumSize(QSize(400, 300));
|
||||||
|
QFile file(":/ACKNOWLEDGEMENTS.html");
|
||||||
|
file.open(QFile::ReadOnly | QFile::Text);
|
||||||
|
QTextStream stream(&file);
|
||||||
|
g_acknowlegementsWidget->setHtml(stream.readAll());
|
||||||
|
}
|
||||||
|
g_acknowlegementsWidget->show();
|
||||||
|
g_acknowlegementsWidget->activateWindow();
|
||||||
|
g_acknowlegementsWidget->raise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::SkeletonDocumentWindow::showAbout()
|
||||||
|
{
|
||||||
|
if (!g_aboutWidget) {
|
||||||
|
g_aboutWidget = new AboutWidget;
|
||||||
|
}
|
||||||
|
g_aboutWidget->show();
|
||||||
|
g_aboutWidget->activateWindow();
|
||||||
|
g_aboutWidget->raise();
|
||||||
|
}
|
||||||
|
|
||||||
SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
m_document(nullptr),
|
m_document(nullptr),
|
||||||
m_firstShow(true)
|
m_firstShow(true),
|
||||||
|
m_documentSaved(true)
|
||||||
{
|
{
|
||||||
|
if (!g_logBrowser) {
|
||||||
|
g_logBrowser = new LogBrowser;
|
||||||
|
qInstallMessageHandler(&outputMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_documentWindows.insert(this);
|
||||||
|
|
||||||
m_document = new SkeletonDocument;
|
m_document = new SkeletonDocument;
|
||||||
|
|
||||||
QVBoxLayout *toolButtonLayout = new QVBoxLayout;
|
QVBoxLayout *toolButtonLayout = new QVBoxLayout;
|
||||||
|
@ -47,13 +106,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
toolButtonLayout->addWidget(zoomInButton);
|
toolButtonLayout->addWidget(zoomInButton);
|
||||||
toolButtonLayout->addWidget(zoomOutButton);
|
toolButtonLayout->addWidget(zoomOutButton);
|
||||||
|
|
||||||
QLabel *dust3dJezzasoftLabel = new QLabel;
|
QLabel *verticalLogoLabel = new QLabel;
|
||||||
QImage dust3dJezzasoftImage;
|
QImage verticalLogoImage;
|
||||||
dust3dJezzasoftImage.load(":/resources/dust3d_jezzasoft.png");
|
verticalLogoImage.load(":/resources/dust3d_vertical.png");
|
||||||
dust3dJezzasoftLabel->setPixmap(QPixmap::fromImage(dust3dJezzasoftImage));
|
verticalLogoLabel->setPixmap(QPixmap::fromImage(verticalLogoImage));
|
||||||
|
|
||||||
QHBoxLayout *logoLayout = new QHBoxLayout;
|
QHBoxLayout *logoLayout = new QHBoxLayout;
|
||||||
logoLayout->addWidget(dust3dJezzasoftLabel);
|
logoLayout->addWidget(verticalLogoLabel);
|
||||||
logoLayout->setContentsMargins(0, 0, 0, 0);
|
logoLayout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
|
||||||
QVBoxLayout *mainLeftLayout = new QVBoxLayout;
|
QVBoxLayout *mainLeftLayout = new QVBoxLayout;
|
||||||
|
@ -65,9 +124,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
mainLeftLayout->addSpacing(10);
|
mainLeftLayout->addSpacing(10);
|
||||||
|
|
||||||
SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_document);
|
SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_document);
|
||||||
|
m_graphicsWidget = graphicsWidget;
|
||||||
|
|
||||||
SkeletonGraphicsContainerWidget *containerWidget = new SkeletonGraphicsContainerWidget;
|
SkeletonGraphicsContainerWidget *containerWidget = new SkeletonGraphicsContainerWidget;
|
||||||
|
|
||||||
containerWidget->setGraphicsWidget(graphicsWidget);
|
containerWidget->setGraphicsWidget(graphicsWidget);
|
||||||
QGridLayout *containerLayout = new QGridLayout;
|
QGridLayout *containerLayout = new QGridLayout;
|
||||||
containerLayout->setSpacing(0);
|
containerLayout->setSpacing(0);
|
||||||
|
@ -103,6 +162,147 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
setCentralWidget(centralWidget);
|
setCentralWidget(centralWidget);
|
||||||
setWindowTitle(tr("Dust3D"));
|
setWindowTitle(tr("Dust3D"));
|
||||||
|
|
||||||
|
m_fileMenu = menuBar()->addMenu(tr("File"));
|
||||||
|
|
||||||
|
m_newWindowAction = new QAction(tr("New Window"), this);
|
||||||
|
connect(m_newWindowAction, &QAction::triggered, this, &SkeletonDocumentWindow::newWindow);
|
||||||
|
m_fileMenu->addAction(m_newWindowAction);
|
||||||
|
|
||||||
|
m_newDocumentAction = new QAction(tr("New"), this);
|
||||||
|
connect(m_newDocumentAction, &QAction::triggered, this, &SkeletonDocumentWindow::newDocument);
|
||||||
|
m_fileMenu->addAction(m_newDocumentAction);
|
||||||
|
|
||||||
|
m_openAction = new QAction(tr("Open..."), this);
|
||||||
|
connect(m_openAction, &QAction::triggered, this, &SkeletonDocumentWindow::open);
|
||||||
|
m_fileMenu->addAction(m_openAction);
|
||||||
|
|
||||||
|
m_saveAction = new QAction(tr("Save"), this);
|
||||||
|
connect(m_saveAction, &QAction::triggered, this, &SkeletonDocumentWindow::save);
|
||||||
|
m_fileMenu->addAction(m_saveAction);
|
||||||
|
|
||||||
|
m_saveAsAction = new QAction(tr("Save As..."), this);
|
||||||
|
connect(m_saveAsAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAs);
|
||||||
|
m_fileMenu->addAction(m_saveAsAction);
|
||||||
|
|
||||||
|
m_saveAllAction = new QAction(tr("Save All"), this);
|
||||||
|
connect(m_saveAllAction, &QAction::triggered, this, &SkeletonDocumentWindow::saveAll);
|
||||||
|
m_fileMenu->addAction(m_saveAllAction);
|
||||||
|
|
||||||
|
m_fileMenu->addSeparator();
|
||||||
|
|
||||||
|
m_exportAction = new QAction(tr("Export..."), this);
|
||||||
|
connect(m_exportAction, &QAction::triggered, this, &SkeletonDocumentWindow::exportResult);
|
||||||
|
m_fileMenu->addAction(m_exportAction);
|
||||||
|
|
||||||
|
m_changeTurnaroundAction = new QAction(tr("Change Turnaround..."), this);
|
||||||
|
connect(m_changeTurnaroundAction, &QAction::triggered, this, &SkeletonDocumentWindow::changeTurnaround);
|
||||||
|
m_fileMenu->addAction(m_changeTurnaroundAction);
|
||||||
|
|
||||||
|
connect(m_fileMenu, &QMenu::aboutToShow, [=]() {
|
||||||
|
m_exportAction->setEnabled(m_graphicsWidget->hasItems());
|
||||||
|
});
|
||||||
|
|
||||||
|
m_editMenu = menuBar()->addMenu(tr("Edit"));
|
||||||
|
|
||||||
|
m_addAction = new QAction(tr("Add..."), this);
|
||||||
|
connect(m_addAction, &QAction::triggered, [=]() {
|
||||||
|
m_document->setEditMode(SkeletonDocumentEditMode::Add);
|
||||||
|
});
|
||||||
|
m_editMenu->addAction(m_addAction);
|
||||||
|
|
||||||
|
m_undoAction = new QAction(tr("Undo"), this);
|
||||||
|
connect(m_undoAction, &QAction::triggered, m_document, &SkeletonDocument::undo);
|
||||||
|
m_editMenu->addAction(m_undoAction);
|
||||||
|
|
||||||
|
m_redoAction = new QAction(tr("Redo"), this);
|
||||||
|
connect(m_redoAction, &QAction::triggered, m_document, &SkeletonDocument::redo);
|
||||||
|
m_editMenu->addAction(m_redoAction);
|
||||||
|
|
||||||
|
m_deleteAction = new QAction(tr("Delete"), this);
|
||||||
|
connect(m_deleteAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::deleteSelected);
|
||||||
|
m_editMenu->addAction(m_deleteAction);
|
||||||
|
|
||||||
|
m_cutAction = new QAction(tr("Cut"), this);
|
||||||
|
connect(m_cutAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::cut);
|
||||||
|
m_editMenu->addAction(m_cutAction);
|
||||||
|
|
||||||
|
m_copyAction = new QAction(tr("Copy"), this);
|
||||||
|
connect(m_copyAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::copy);
|
||||||
|
m_editMenu->addAction(m_copyAction);
|
||||||
|
|
||||||
|
m_pasteAction = new QAction(tr("Paste"), this);
|
||||||
|
connect(m_pasteAction, &QAction::triggered, m_document, &SkeletonDocument::paste);
|
||||||
|
m_editMenu->addAction(m_pasteAction);
|
||||||
|
|
||||||
|
m_flipHorizontallyAction = new QAction(tr("H Flip"), this);
|
||||||
|
connect(m_flipHorizontallyAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::flipHorizontally);
|
||||||
|
m_editMenu->addAction(m_flipHorizontallyAction);
|
||||||
|
|
||||||
|
m_flipVerticallyAction = new QAction(tr("V Flip"), this);
|
||||||
|
connect(m_flipVerticallyAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::flipVertically);
|
||||||
|
m_editMenu->addAction(m_flipVerticallyAction);
|
||||||
|
|
||||||
|
m_selectAllAction = new QAction(tr("Select All"), this);
|
||||||
|
connect(m_selectAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::selectAll);
|
||||||
|
m_editMenu->addAction(m_selectAllAction);
|
||||||
|
|
||||||
|
m_selectPartAllAction = new QAction(tr("Select Part"), this);
|
||||||
|
connect(m_selectPartAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::selectPartAll);
|
||||||
|
m_editMenu->addAction(m_selectPartAllAction);
|
||||||
|
|
||||||
|
m_unselectAllAction = new QAction(tr("Unselect All"), this);
|
||||||
|
connect(m_unselectAllAction, &QAction::triggered, m_graphicsWidget, &SkeletonGraphicsWidget::unselectAll);
|
||||||
|
m_editMenu->addAction(m_unselectAllAction);
|
||||||
|
|
||||||
|
connect(m_editMenu, &QMenu::aboutToShow, [=]() {
|
||||||
|
m_undoAction->setEnabled(m_document->undoable());
|
||||||
|
m_redoAction->setEnabled(m_document->redoable());
|
||||||
|
m_deleteAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||||
|
m_cutAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||||
|
m_copyAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||||
|
m_pasteAction->setEnabled(m_document->hasPastableContentInClipboard());
|
||||||
|
m_flipHorizontallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
||||||
|
m_flipVerticallyAction->setEnabled(m_graphicsWidget->hasMultipleSelection());
|
||||||
|
m_selectAllAction->setEnabled(m_graphicsWidget->hasItems());
|
||||||
|
m_selectPartAllAction->setEnabled(m_graphicsWidget->hasItems());
|
||||||
|
m_unselectAllAction->setEnabled(m_graphicsWidget->hasSelection());
|
||||||
|
});
|
||||||
|
|
||||||
|
m_viewMenu = menuBar()->addMenu(tr("View"));
|
||||||
|
|
||||||
|
m_resetModelWidgetPosAction = new QAction(tr("Show Model"), this);
|
||||||
|
connect(m_resetModelWidgetPosAction, &QAction::triggered, [=]() {
|
||||||
|
QRect parentRect = QRect(QPoint(0, 0), m_modelWidget->parentWidget()->size());
|
||||||
|
if (!parentRect.contains(m_modelWidget->geometry().center())) {
|
||||||
|
m_modelWidget->move(16, 16);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
m_viewMenu->addAction(m_resetModelWidgetPosAction);
|
||||||
|
|
||||||
|
m_showDebugDialogAction = new QAction(tr("Show Debug Dialog"), this);
|
||||||
|
connect(m_showDebugDialogAction, &QAction::triggered, g_logBrowser, &LogBrowser::showDialog);
|
||||||
|
m_viewMenu->addAction(m_showDebugDialogAction);
|
||||||
|
|
||||||
|
m_helpMenu = menuBar()->addMenu(tr("Help"));
|
||||||
|
|
||||||
|
m_viewSourceAction = new QAction(tr("Fork me on GitHub"), this);
|
||||||
|
connect(m_viewSourceAction, &QAction::triggered, this, &SkeletonDocumentWindow::viewSource);
|
||||||
|
m_helpMenu->addAction(m_viewSourceAction);
|
||||||
|
|
||||||
|
m_helpMenu->addSeparator();
|
||||||
|
|
||||||
|
m_aboutAction = new QAction(tr("About"), this);
|
||||||
|
connect(m_aboutAction, &QAction::triggered, this, &SkeletonDocumentWindow::about);
|
||||||
|
m_helpMenu->addAction(m_aboutAction);
|
||||||
|
|
||||||
|
m_reportIssuesAction = new QAction(tr("Report Issues"), this);
|
||||||
|
connect(m_reportIssuesAction, &QAction::triggered, this, &SkeletonDocumentWindow::reportIssues);
|
||||||
|
m_helpMenu->addAction(m_reportIssuesAction);
|
||||||
|
|
||||||
|
m_seeAcknowlegementsAction = new QAction(tr("Acknowlegements"), this);
|
||||||
|
connect(m_seeAcknowlegementsAction, &QAction::triggered, this, &SkeletonDocumentWindow::seeAcknowlegements);
|
||||||
|
m_helpMenu->addAction(m_seeAcknowlegementsAction);
|
||||||
|
|
||||||
connect(containerWidget, &SkeletonGraphicsContainerWidget::containerSizeChanged,
|
connect(containerWidget, &SkeletonGraphicsContainerWidget::containerSizeChanged,
|
||||||
graphicsWidget, &SkeletonGraphicsWidget::canvasResized);
|
graphicsWidget, &SkeletonGraphicsWidget::canvasResized);
|
||||||
|
|
||||||
|
@ -146,10 +346,14 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_document, &SkeletonDocument::undo);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_document, &SkeletonDocument::undo);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_document, &SkeletonDocument::redo);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_document, &SkeletonDocument::redo);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::paste, m_document, &SkeletonDocument::paste);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::paste, m_document, &SkeletonDocument::paste);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &SkeletonDocumentWindow::changeTurnaround);
|
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeBegin, m_document, &SkeletonDocument::batchChangeBegin);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeBegin, m_document, &SkeletonDocument::batchChangeBegin);
|
||||||
connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeEnd, m_document, &SkeletonDocument::batchChangeEnd);
|
connect(graphicsWidget, &SkeletonGraphicsWidget::batchChangeEnd, m_document, &SkeletonDocument::batchChangeEnd);
|
||||||
|
|
||||||
|
connect(graphicsWidget, &SkeletonGraphicsWidget::changeTurnaround, this, &SkeletonDocumentWindow::changeTurnaround);
|
||||||
|
connect(graphicsWidget, &SkeletonGraphicsWidget::save, this, &SkeletonDocumentWindow::save);
|
||||||
|
connect(graphicsWidget, &SkeletonGraphicsWidget::open, this, &SkeletonDocumentWindow::open);
|
||||||
|
connect(graphicsWidget, &SkeletonGraphicsWidget::exportResult, this, &SkeletonDocumentWindow::exportResult);
|
||||||
|
|
||||||
connect(m_document, &SkeletonDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
|
connect(m_document, &SkeletonDocument::nodeAdded, graphicsWidget, &SkeletonGraphicsWidget::nodeAdded);
|
||||||
connect(m_document, &SkeletonDocument::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved);
|
connect(m_document, &SkeletonDocument::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved);
|
||||||
connect(m_document, &SkeletonDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded);
|
connect(m_document, &SkeletonDocument::edgeAdded, graphicsWidget, &SkeletonGraphicsWidget::edgeAdded);
|
||||||
|
@ -157,12 +361,14 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
connect(m_document, &SkeletonDocument::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged);
|
connect(m_document, &SkeletonDocument::nodeRadiusChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeRadiusChanged);
|
||||||
connect(m_document, &SkeletonDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
|
connect(m_document, &SkeletonDocument::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
|
||||||
connect(m_document, &SkeletonDocument::partVisibleStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged);
|
connect(m_document, &SkeletonDocument::partVisibleStateChanged, graphicsWidget, &SkeletonGraphicsWidget::partVisibleStateChanged);
|
||||||
|
connect(m_document, &SkeletonDocument::cleanup, graphicsWidget, &SkeletonGraphicsWidget::removeAllContent);
|
||||||
|
|
||||||
connect(m_document, &SkeletonDocument::partListChanged, partListWidget, &SkeletonPartListWidget::partListChanged);
|
connect(m_document, &SkeletonDocument::partListChanged, partListWidget, &SkeletonPartListWidget::partListChanged);
|
||||||
connect(m_document, &SkeletonDocument::partPreviewChanged, partListWidget, &SkeletonPartListWidget::partPreviewChanged);
|
connect(m_document, &SkeletonDocument::partPreviewChanged, partListWidget, &SkeletonPartListWidget::partPreviewChanged);
|
||||||
connect(m_document, &SkeletonDocument::partLockStateChanged, partListWidget, &SkeletonPartListWidget::partLockStateChanged);
|
connect(m_document, &SkeletonDocument::partLockStateChanged, partListWidget, &SkeletonPartListWidget::partLockStateChanged);
|
||||||
connect(m_document, &SkeletonDocument::partVisibleStateChanged, partListWidget, &SkeletonPartListWidget::partVisibleStateChanged);
|
connect(m_document, &SkeletonDocument::partVisibleStateChanged, partListWidget, &SkeletonPartListWidget::partVisibleStateChanged);
|
||||||
connect(m_document, &SkeletonDocument::partSubdivStateChanged, partListWidget, &SkeletonPartListWidget::partSubdivStateChanged);
|
connect(m_document, &SkeletonDocument::partSubdivStateChanged, partListWidget, &SkeletonPartListWidget::partSubdivStateChanged);
|
||||||
|
connect(m_document, &SkeletonDocument::cleanup, partListWidget, &SkeletonPartListWidget::partListChanged);
|
||||||
|
|
||||||
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
|
connect(m_document, &SkeletonDocument::skeletonChanged, m_document, &SkeletonDocument::generateMesh);
|
||||||
|
|
||||||
|
@ -174,14 +380,118 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
||||||
m_modelWidget->setCursor(graphicsWidget->cursor());
|
m_modelWidget->setCursor(graphicsWidget->cursor());
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(this, &SkeletonDocumentWindow::initialized, m_document, [=]() {
|
connect(m_document, &SkeletonDocument::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||||
m_document->saveSnapshot();
|
connect(m_document, &SkeletonDocument::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||||
graphicsWidget->setFocus();
|
|
||||||
});
|
|
||||||
|
|
||||||
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
|
connect(this, &SkeletonDocumentWindow::initialized, m_document, &SkeletonDocument::uiReady);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SkeletonDocumentWindow *SkeletonDocumentWindow::createDocumentWindow()
|
||||||
|
{
|
||||||
|
SkeletonDocumentWindow *documentWindow = new SkeletonDocumentWindow();
|
||||||
|
documentWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
documentWindow->showMaximized();
|
||||||
|
return documentWindow;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::closeEvent(QCloseEvent *event)
|
||||||
|
{
|
||||||
|
if (m_documentSaved) {
|
||||||
|
event->accept();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||||
|
APP_NAME,
|
||||||
|
tr("Do you really want to close while there are unsaved changes?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (answer == QMessageBox::Yes)
|
||||||
|
event->accept();
|
||||||
|
else
|
||||||
|
event->ignore();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::setCurrentFilename(const QString &filename)
|
||||||
|
{
|
||||||
|
m_currentFilename = filename;
|
||||||
|
m_documentSaved = true;
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::updateTitle()
|
||||||
|
{
|
||||||
|
QString appName = APP_NAME;
|
||||||
|
QString appVer = APP_HUMAN_VER;
|
||||||
|
setWindowTitle(QString("%1 %2 %3%4").arg(appName).arg(appVer).arg(m_currentFilename).arg(m_documentSaved ? "" : "*"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::documentChanged()
|
||||||
|
{
|
||||||
|
if (m_documentSaved) {
|
||||||
|
m_documentSaved = false;
|
||||||
|
updateTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::newWindow()
|
||||||
|
{
|
||||||
|
SkeletonDocumentWindow::createDocumentWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::newDocument()
|
||||||
|
{
|
||||||
|
if (!m_documentSaved) {
|
||||||
|
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||||
|
APP_NAME,
|
||||||
|
tr("Do you really want to create new document and lose the unsaved changes?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (answer != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_document->reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::saveAs()
|
||||||
|
{
|
||||||
|
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||||
|
tr("Dust3D Document (*.ds3)"));
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveTo(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::saveAll()
|
||||||
|
{
|
||||||
|
for (auto &window: g_documentWindows) {
|
||||||
|
window->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::viewSource()
|
||||||
|
{
|
||||||
|
QString url = APP_REPOSITORY_URL;
|
||||||
|
qDebug() << "viewSource:" << url;
|
||||||
|
QDesktopServices::openUrl(QUrl(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::about()
|
||||||
|
{
|
||||||
|
SkeletonDocumentWindow::showAbout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::reportIssues()
|
||||||
|
{
|
||||||
|
QString url = APP_ISSUES_URL;
|
||||||
|
qDebug() << "reportIssues:" << url;
|
||||||
|
QDesktopServices::openUrl(QUrl(url));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::seeAcknowlegements()
|
||||||
|
{
|
||||||
|
SkeletonDocumentWindow::showAcknowlegements();
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonDocumentWindow::initButton(QPushButton *button)
|
void SkeletonDocumentWindow::initButton(QPushButton *button)
|
||||||
{
|
{
|
||||||
button->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
button->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||||
|
@ -191,6 +501,7 @@ void SkeletonDocumentWindow::initButton(QPushButton *button)
|
||||||
|
|
||||||
SkeletonDocumentWindow::~SkeletonDocumentWindow()
|
SkeletonDocumentWindow::~SkeletonDocumentWindow()
|
||||||
{
|
{
|
||||||
|
g_documentWindows.erase(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonDocumentWindow::showEvent(QShowEvent *event)
|
void SkeletonDocumentWindow::showEvent(QShowEvent *event)
|
||||||
|
@ -198,14 +509,16 @@ void SkeletonDocumentWindow::showEvent(QShowEvent *event)
|
||||||
QMainWindow::showEvent(event);
|
QMainWindow::showEvent(event);
|
||||||
if (m_firstShow) {
|
if (m_firstShow) {
|
||||||
m_firstShow = false;
|
m_firstShow = false;
|
||||||
|
updateTitle();
|
||||||
|
m_document->saveSnapshot();
|
||||||
|
m_graphicsWidget->setFocus();
|
||||||
emit initialized();
|
emit initialized();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonDocumentWindow::changeTurnaround()
|
void SkeletonDocumentWindow::changeTurnaround()
|
||||||
{
|
{
|
||||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"),
|
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
|
||||||
QString(),
|
|
||||||
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
|
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
|
||||||
if (fileName.isEmpty())
|
if (fileName.isEmpty())
|
||||||
return;
|
return;
|
||||||
|
@ -214,3 +527,102 @@ void SkeletonDocumentWindow::changeTurnaround()
|
||||||
return;
|
return;
|
||||||
m_document->updateTurnaround(image);
|
m_document->updateTurnaround(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::save()
|
||||||
|
{
|
||||||
|
saveTo(m_currentFilename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::saveTo(const QString &saveAsFilename)
|
||||||
|
{
|
||||||
|
QString filename = saveAsFilename;
|
||||||
|
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||||
|
tr("Dust3D Document (*.ds3)"));
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
|
|
||||||
|
Ds3FileWriter ds3Writer;
|
||||||
|
|
||||||
|
QByteArray modelXml;
|
||||||
|
QXmlStreamWriter stream(&modelXml);
|
||||||
|
SkeletonSnapshot snapshot;
|
||||||
|
m_document->toSnapshot(&snapshot);
|
||||||
|
saveSkeletonToXmlStream(&snapshot, &stream);
|
||||||
|
if (modelXml.size() > 0)
|
||||||
|
ds3Writer.add("model.xml", "model", &modelXml);
|
||||||
|
|
||||||
|
QByteArray imageByteArray;
|
||||||
|
QBuffer pngBuffer(&imageByteArray);
|
||||||
|
if (!m_document->turnaround.isNull()) {
|
||||||
|
pngBuffer.open(QIODevice::WriteOnly);
|
||||||
|
m_document->turnaround.save(&pngBuffer, "PNG");
|
||||||
|
if (imageByteArray.size() > 0)
|
||||||
|
ds3Writer.add("canvas.png", "asset", &imageByteArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ds3Writer.save(filename)) {
|
||||||
|
setCurrentFilename(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::open()
|
||||||
|
{
|
||||||
|
if (!m_documentSaved) {
|
||||||
|
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||||
|
APP_NAME,
|
||||||
|
tr("Do you really want to open another file and lose the unsaved changes?"),
|
||||||
|
QMessageBox::Yes | QMessageBox::No);
|
||||||
|
if (answer != QMessageBox::Yes)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString filename = QFileDialog::getOpenFileName(this, QString(), QString(),
|
||||||
|
tr("Dust3D Document (*.ds3)"));
|
||||||
|
if (filename.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
|
Ds3FileReader ds3Reader(filename);
|
||||||
|
for (int i = 0; i < ds3Reader.items().size(); ++i) {
|
||||||
|
Ds3ReaderItem item = ds3Reader.items().at(i);
|
||||||
|
if (item.type == "model") {
|
||||||
|
QByteArray data;
|
||||||
|
ds3Reader.loadItem(item.name, &data);
|
||||||
|
QXmlStreamReader stream(data);
|
||||||
|
SkeletonSnapshot snapshot;
|
||||||
|
loadSkeletonFromXmlStream(&snapshot, stream);
|
||||||
|
m_document->fromSnapshot(snapshot);
|
||||||
|
} else if (item.type == "asset") {
|
||||||
|
if (item.name == "canvas.png") {
|
||||||
|
QByteArray data;
|
||||||
|
ds3Reader.loadItem(item.name, &data);
|
||||||
|
QImage image = QImage::fromData(data, "PNG");
|
||||||
|
m_document->updateTurnaround(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
|
||||||
|
setCurrentFilename(filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SkeletonDocumentWindow::exportResult()
|
||||||
|
{
|
||||||
|
QString filename = QFileDialog::getSaveFileName(this, QString(), QString(),
|
||||||
|
tr("Wavefront (*.obj)"));
|
||||||
|
if (filename.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QApplication::setOverrideCursor(Qt::WaitCursor);
|
||||||
|
m_modelWidget->exportMeshAsObj(filename);
|
||||||
|
QApplication::restoreOverrideCursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,15 @@
|
||||||
#include <QMainWindow>
|
#include <QMainWindow>
|
||||||
#include <QShowEvent>
|
#include <QShowEvent>
|
||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
|
#include <QString>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QAction>
|
||||||
|
#include <QTextBrowser>
|
||||||
#include "skeletondocument.h"
|
#include "skeletondocument.h"
|
||||||
#include "modelwidget.h"
|
#include "modelwidget.h"
|
||||||
|
|
||||||
|
class SkeletonGraphicsWidget;
|
||||||
|
|
||||||
class SkeletonDocumentWindow : public QMainWindow
|
class SkeletonDocumentWindow : public QMainWindow
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
@ -14,16 +20,72 @@ signals:
|
||||||
public:
|
public:
|
||||||
SkeletonDocumentWindow();
|
SkeletonDocumentWindow();
|
||||||
~SkeletonDocumentWindow();
|
~SkeletonDocumentWindow();
|
||||||
|
static SkeletonDocumentWindow *createDocumentWindow();
|
||||||
|
static void showAcknowlegements();
|
||||||
|
static void showAbout();
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent *event);
|
void showEvent(QShowEvent *event);
|
||||||
|
void closeEvent(QCloseEvent *event);
|
||||||
public slots:
|
public slots:
|
||||||
void changeTurnaround();
|
void changeTurnaround();
|
||||||
|
void save();
|
||||||
|
void saveTo(const QString &saveAsFilename);
|
||||||
|
void open();
|
||||||
|
void exportResult();
|
||||||
|
void newWindow();
|
||||||
|
void newDocument();
|
||||||
|
void saveAs();
|
||||||
|
void saveAll();
|
||||||
|
void viewSource();
|
||||||
|
void about();
|
||||||
|
void reportIssues();
|
||||||
|
void seeAcknowlegements();
|
||||||
|
void documentChanged();
|
||||||
private:
|
private:
|
||||||
void initButton(QPushButton *button);
|
void initButton(QPushButton *button);
|
||||||
|
void setCurrentFilename(const QString &filename);
|
||||||
|
void updateTitle();
|
||||||
private:
|
private:
|
||||||
SkeletonDocument *m_document;
|
SkeletonDocument *m_document;
|
||||||
bool m_firstShow;
|
bool m_firstShow;
|
||||||
ModelWidget *m_modelWidget;
|
ModelWidget *m_modelWidget;
|
||||||
|
SkeletonGraphicsWidget *m_graphicsWidget;
|
||||||
|
QString m_currentFilename;
|
||||||
|
bool m_documentSaved;
|
||||||
|
|
||||||
|
QMenu *m_fileMenu;
|
||||||
|
QAction *m_newWindowAction;
|
||||||
|
QAction *m_newDocumentAction;
|
||||||
|
QAction *m_openAction;
|
||||||
|
QAction *m_saveAction;
|
||||||
|
QAction *m_saveAsAction;
|
||||||
|
QAction *m_saveAllAction;
|
||||||
|
QAction *m_exportAction;
|
||||||
|
QAction *m_changeTurnaroundAction;
|
||||||
|
|
||||||
|
QMenu *m_editMenu;
|
||||||
|
QAction *m_addAction;
|
||||||
|
QAction *m_undoAction;
|
||||||
|
QAction *m_redoAction;
|
||||||
|
QAction *m_deleteAction;
|
||||||
|
QAction *m_cutAction;
|
||||||
|
QAction *m_copyAction;
|
||||||
|
QAction *m_pasteAction;
|
||||||
|
QAction *m_flipHorizontallyAction;
|
||||||
|
QAction *m_flipVerticallyAction;
|
||||||
|
QAction *m_selectAllAction;
|
||||||
|
QAction *m_selectPartAllAction;
|
||||||
|
QAction *m_unselectAllAction;
|
||||||
|
|
||||||
|
QMenu *m_viewMenu;
|
||||||
|
QAction *m_resetModelWidgetPosAction;
|
||||||
|
QAction *m_showDebugDialogAction;
|
||||||
|
|
||||||
|
QMenu *m_helpMenu;
|
||||||
|
QAction *m_viewSourceAction;
|
||||||
|
QAction *m_aboutAction;
|
||||||
|
QAction *m_reportIssuesAction;
|
||||||
|
QAction *m_seeAcknowlegementsAction;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -46,10 +46,12 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
|
||||||
|
|
||||||
m_cursorNodeItem = new SkeletonGraphicsNodeItem();
|
m_cursorNodeItem = new SkeletonGraphicsNodeItem();
|
||||||
m_cursorNodeItem->hide();
|
m_cursorNodeItem->hide();
|
||||||
|
m_cursorNodeItem->setData(0, "cursorNode");
|
||||||
scene()->addItem(m_cursorNodeItem);
|
scene()->addItem(m_cursorNodeItem);
|
||||||
|
|
||||||
m_cursorEdgeItem = new SkeletonGraphicsEdgeItem();
|
m_cursorEdgeItem = new SkeletonGraphicsEdgeItem();
|
||||||
m_cursorEdgeItem->hide();
|
m_cursorEdgeItem->hide();
|
||||||
|
m_cursorEdgeItem->setData(0, "cursorEdge");
|
||||||
scene()->addItem(m_cursorEdgeItem);
|
scene()->addItem(m_cursorEdgeItem);
|
||||||
|
|
||||||
m_selectionItem = new SkeletonGraphicsSelectionItem();
|
m_selectionItem = new SkeletonGraphicsSelectionItem();
|
||||||
|
@ -77,7 +79,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
||||||
|
|
||||||
QMenu contextMenu(this);
|
QMenu contextMenu(this);
|
||||||
|
|
||||||
QAction addAction(tr("Add.."), this);
|
QAction addAction(tr("Add..."), this);
|
||||||
connect(&addAction, &QAction::triggered, [=]() {
|
connect(&addAction, &QAction::triggered, [=]() {
|
||||||
emit setEditMode(SkeletonDocumentEditMode::Add);
|
emit setEditMode(SkeletonDocumentEditMode::Add);
|
||||||
});
|
});
|
||||||
|
@ -96,19 +98,19 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction deleteAction(tr("Delete"), this);
|
QAction deleteAction(tr("Delete"), this);
|
||||||
if (!m_rangeSelectionSet.empty()) {
|
if (hasSelection()) {
|
||||||
connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected);
|
connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected);
|
||||||
contextMenu.addAction(&deleteAction);
|
contextMenu.addAction(&deleteAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction cutAction(tr("Cut"), this);
|
QAction cutAction(tr("Cut"), this);
|
||||||
if (!m_rangeSelectionSet.empty()) {
|
if (hasSelection()) {
|
||||||
connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut);
|
connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut);
|
||||||
contextMenu.addAction(&cutAction);
|
contextMenu.addAction(&cutAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction copyAction(tr("Copy"), this);
|
QAction copyAction(tr("Copy"), this);
|
||||||
if (!m_rangeSelectionSet.empty()) {
|
if (hasSelection()) {
|
||||||
connect(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
|
connect(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
|
||||||
contextMenu.addAction(©Action);
|
contextMenu.addAction(©Action);
|
||||||
}
|
}
|
||||||
|
@ -120,38 +122,44 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction flipHorizontallyAction(tr("H Flip"), this);
|
QAction flipHorizontallyAction(tr("H Flip"), this);
|
||||||
if (!m_rangeSelectionSet.empty() && !isSingleNodeSelected()) {
|
if (hasMultipleSelection()) {
|
||||||
connect(&flipHorizontallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipHorizontally);
|
connect(&flipHorizontallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipHorizontally);
|
||||||
contextMenu.addAction(&flipHorizontallyAction);
|
contextMenu.addAction(&flipHorizontallyAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction flipVerticallyAction(tr("V Flip"), this);
|
QAction flipVerticallyAction(tr("V Flip"), this);
|
||||||
if (!m_rangeSelectionSet.empty() && !isSingleNodeSelected()) {
|
if (hasMultipleSelection()) {
|
||||||
connect(&flipVerticallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipVertically);
|
connect(&flipVerticallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipVertically);
|
||||||
contextMenu.addAction(&flipVerticallyAction);
|
contextMenu.addAction(&flipVerticallyAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction selectAllAction(tr("Select All"), this);
|
QAction selectAllAction(tr("Select All"), this);
|
||||||
if (!nodeItemMap.empty()) {
|
if (hasItems()) {
|
||||||
connect(&selectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectAll);
|
connect(&selectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectAll);
|
||||||
contextMenu.addAction(&selectAllAction);
|
contextMenu.addAction(&selectAllAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction selectPartAllAction(tr("Select Part"), this);
|
QAction selectPartAllAction(tr("Select Part"), this);
|
||||||
if (!nodeItemMap.empty()) {
|
if (hasItems()) {
|
||||||
connect(&selectPartAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectPartAll);
|
connect(&selectPartAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectPartAll);
|
||||||
contextMenu.addAction(&selectPartAllAction);
|
contextMenu.addAction(&selectPartAllAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
QAction unselectAllAction(tr("Unselect All"), this);
|
QAction unselectAllAction(tr("Unselect All"), this);
|
||||||
if (!m_rangeSelectionSet.empty()) {
|
if (hasSelection()) {
|
||||||
connect(&unselectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::unselectAll);
|
connect(&unselectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::unselectAll);
|
||||||
contextMenu.addAction(&unselectAllAction);
|
contextMenu.addAction(&unselectAllAction);
|
||||||
}
|
}
|
||||||
|
|
||||||
contextMenu.addSeparator();
|
contextMenu.addSeparator();
|
||||||
|
|
||||||
QAction changeTurnaroundAction(tr("Change Turnaround.."), this);
|
QAction exportResultAction(tr("Export..."), this);
|
||||||
|
if (hasItems()) {
|
||||||
|
connect(&exportResultAction, &QAction::triggered, this, &SkeletonGraphicsWidget::exportResult);
|
||||||
|
contextMenu.addAction(&exportResultAction);
|
||||||
|
}
|
||||||
|
|
||||||
|
QAction changeTurnaroundAction(tr("Change Turnaround..."), this);
|
||||||
connect(&changeTurnaroundAction, &QAction::triggered, [=]() {
|
connect(&changeTurnaroundAction, &QAction::triggered, [=]() {
|
||||||
emit changeTurnaround();
|
emit changeTurnaround();
|
||||||
});
|
});
|
||||||
|
@ -160,6 +168,21 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
||||||
contextMenu.exec(mapToGlobal(pos));
|
contextMenu.exec(mapToGlobal(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SkeletonGraphicsWidget::hasSelection()
|
||||||
|
{
|
||||||
|
return !m_rangeSelectionSet.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SkeletonGraphicsWidget::hasItems()
|
||||||
|
{
|
||||||
|
return !nodeItemMap.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SkeletonGraphicsWidget::hasMultipleSelection()
|
||||||
|
{
|
||||||
|
return !m_rangeSelectionSet.empty() && !isSingleNodeSelected();
|
||||||
|
}
|
||||||
|
|
||||||
void SkeletonGraphicsWidget::updateItems()
|
void SkeletonGraphicsWidget::updateItems()
|
||||||
{
|
{
|
||||||
for (auto nodeItemIt = nodeItemMap.begin(); nodeItemIt != nodeItemMap.end(); nodeItemIt++) {
|
for (auto nodeItemIt = nodeItemMap.begin(); nodeItemIt != nodeItemMap.end(); nodeItemIt++) {
|
||||||
|
@ -669,6 +692,10 @@ bool SkeletonGraphicsWidget::mouseDoubleClick(QMouseEvent *event)
|
||||||
selectPartAll();
|
selectPartAll();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
||||||
|
emit open();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -734,6 +761,10 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event)
|
||||||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
||||||
emit paste();
|
emit paste();
|
||||||
}
|
}
|
||||||
|
} else if (event->key() == Qt::Key_S) {
|
||||||
|
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
||||||
|
emit save();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -1153,6 +1184,33 @@ void SkeletonGraphicsWidget::copy()
|
||||||
clipboard->setText(snapshotXml);
|
clipboard->setText(snapshotXml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SkeletonGraphicsWidget::removeAllContent()
|
||||||
|
{
|
||||||
|
nodeItemMap.clear();
|
||||||
|
edgeItemMap.clear();
|
||||||
|
m_rangeSelectionSet.clear();
|
||||||
|
m_hoveredEdgeItem = nullptr;
|
||||||
|
m_hoveredNodeItem = nullptr;
|
||||||
|
m_addFromNodeItem = nullptr;
|
||||||
|
m_cursorEdgeItem->hide();
|
||||||
|
m_cursorNodeItem->hide();
|
||||||
|
std::set<QGraphicsItem *> contentItems;
|
||||||
|
for (const auto &it: items()) {
|
||||||
|
QGraphicsItem *item = it;
|
||||||
|
if (item->data(0) == "node") {
|
||||||
|
contentItems.insert(item);
|
||||||
|
} else if (item->data(0) == "edge") {
|
||||||
|
contentItems.insert(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto &it: contentItems) {
|
||||||
|
QGraphicsItem *item = it;
|
||||||
|
Q_ASSERT(item != m_cursorEdgeItem);
|
||||||
|
Q_ASSERT(item != m_cursorNodeItem);
|
||||||
|
scene()->removeItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool SkeletonGraphicsWidget::isSingleNodeSelected()
|
bool SkeletonGraphicsWidget::isSingleNodeSelected()
|
||||||
{
|
{
|
||||||
if (m_rangeSelectionSet.size() != 1)
|
if (m_rangeSelectionSet.size() != 1)
|
||||||
|
|
|
@ -266,6 +266,9 @@ signals:
|
||||||
void changeTurnaround();
|
void changeTurnaround();
|
||||||
void batchChangeBegin();
|
void batchChangeBegin();
|
||||||
void batchChangeEnd();
|
void batchChangeEnd();
|
||||||
|
void save();
|
||||||
|
void open();
|
||||||
|
void exportResult();
|
||||||
public:
|
public:
|
||||||
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
||||||
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
||||||
|
@ -281,6 +284,9 @@ public:
|
||||||
void readMergedSkeletonNodeSetFromRangeSelection(std::set<SkeletonGraphicsNodeItem *> *nodeItemSet);
|
void readMergedSkeletonNodeSetFromRangeSelection(std::set<SkeletonGraphicsNodeItem *> *nodeItemSet);
|
||||||
void readSkeletonNodeAndEdgeIdSetFromRangeSelection(std::set<QUuid> *nodeIdSet, std::set<QUuid> *edgeIdSet);
|
void readSkeletonNodeAndEdgeIdSetFromRangeSelection(std::set<QUuid> *nodeIdSet, std::set<QUuid> *edgeIdSet);
|
||||||
bool readSkeletonNodeAndAnyEdgeOfNodeFromRangeSelection(SkeletonGraphicsNodeItem **nodeItem, SkeletonGraphicsEdgeItem **edgeItem);
|
bool readSkeletonNodeAndAnyEdgeOfNodeFromRangeSelection(SkeletonGraphicsNodeItem **nodeItem, SkeletonGraphicsEdgeItem **edgeItem);
|
||||||
|
bool hasSelection();
|
||||||
|
bool hasItems();
|
||||||
|
bool hasMultipleSelection();
|
||||||
protected:
|
protected:
|
||||||
void mouseMoveEvent(QMouseEvent *event);
|
void mouseMoveEvent(QMouseEvent *event);
|
||||||
void wheelEvent(QWheelEvent *event);
|
void wheelEvent(QWheelEvent *event);
|
||||||
|
@ -310,6 +316,7 @@ public slots:
|
||||||
void copy();
|
void copy();
|
||||||
void flipHorizontally();
|
void flipHorizontally();
|
||||||
void flipVertically();
|
void flipVertically();
|
||||||
|
void removeAllContent();
|
||||||
private slots:
|
private slots:
|
||||||
void turnaroundImageReady();
|
void turnaroundImageReady();
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -1,282 +1,2 @@
|
||||||
#include "skeletonsnapshot.h"
|
#include "skeletonsnapshot.h"
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
SkeletonSnapshot::SkeletonSnapshot() :
|
|
||||||
m_boundingBoxResolved(false),
|
|
||||||
m_rootNodeResolved(false)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
void joinNodeAndNeighborsToGroup(std::map<QString, std::vector<QString>> &nodeLinkMap,
|
|
||||||
std::map<QString, int> &nodeGroupMap,
|
|
||||||
const QString &nodeId,
|
|
||||||
int groupId,
|
|
||||||
std::vector<QString> &group)
|
|
||||||
{
|
|
||||||
group.push_back(nodeId);
|
|
||||||
nodeGroupMap[nodeId] = groupId;
|
|
||||||
std::vector<QString> *neighbors = &nodeLinkMap[nodeId];
|
|
||||||
for (size_t i = 0; i < neighbors->size(); i++) {
|
|
||||||
QString neighborNodeId = (*neighbors)[i];
|
|
||||||
if (nodeGroupMap.find(neighborNodeId) != nodeGroupMap.end())
|
|
||||||
continue;
|
|
||||||
nodeGroupMap[neighborNodeId] = groupId;
|
|
||||||
joinNodeAndNeighborsToGroup(nodeLinkMap,
|
|
||||||
nodeGroupMap,
|
|
||||||
neighborNodeId,
|
|
||||||
groupId,
|
|
||||||
group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkeletonSnapshot::splitByConnectivity(std::vector<SkeletonSnapshot> *groups)
|
|
||||||
{
|
|
||||||
std::map<QString, std::vector<QString>> nodeLinkMap;
|
|
||||||
std::map<QString, std::map<QString, QString>>::iterator edgeIterator;
|
|
||||||
std::map<QString, int> neighborCountMap;
|
|
||||||
for (edgeIterator = edges.begin(); edgeIterator != edges.end(); edgeIterator++) {
|
|
||||||
neighborCountMap[edgeIterator->second["from"]]++;
|
|
||||||
neighborCountMap[edgeIterator->second["to"]]++;
|
|
||||||
}
|
|
||||||
int nextNewXnodeId = 1;
|
|
||||||
std::vector<std::pair<QString, QString>> newPendingEdges;
|
|
||||||
for (edgeIterator = edges.begin(); edgeIterator != edges.end(); ) {
|
|
||||||
std::map<QString, QString> *oneNode = &nodes[edgeIterator->second["from"]];
|
|
||||||
std::map<QString, QString> *linkNode = &nodes[edgeIterator->second["to"]];
|
|
||||||
if ("true" != (*oneNode)["isBranch"]) {
|
|
||||||
oneNode = &nodes[edgeIterator->second["to"]];
|
|
||||||
linkNode = &nodes[edgeIterator->second["from"]];
|
|
||||||
if ("true" != (*oneNode)["isBranch"]) {
|
|
||||||
edgeIterator++;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
if (neighborCountMap[(*linkNode)["id"]] < 3) {
|
|
||||||
edgeIterator++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (neighborCountMap[(*linkNode)["id"]] < 3) {
|
|
||||||
edgeIterator++;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("red" == (*oneNode)["sideColorName"]) {
|
|
||||||
QString nodeId = QString("nodex%1").arg(nextNewXnodeId++);
|
|
||||||
std::map<QString, QString> *newNode = &nodes[nodeId];
|
|
||||||
*newNode = *linkNode;
|
|
||||||
(*newNode)["id"] = nodeId;
|
|
||||||
(*newNode)["radius"] = QString("%1").arg((*newNode)["radius"].toFloat() / 5);
|
|
||||||
|
|
||||||
std::map<QString, QString> *pairNode = &nodes[(*oneNode)["nextSidePair"]];
|
|
||||||
QString pairNodeId = QString("nodex%1").arg(nextNewXnodeId++);
|
|
||||||
std::map<QString, QString> *newPairNode = &nodes[pairNodeId];
|
|
||||||
*newPairNode = *pairNode;
|
|
||||||
(*newPairNode)["id"] = pairNodeId;
|
|
||||||
(*newPairNode)["radius"] = QString("%1").arg((*newPairNode)["radius"].toFloat() / 5);
|
|
||||||
|
|
||||||
(*newNode)["nextSidePair"] = pairNodeId;
|
|
||||||
(*newPairNode)["nextSidePair"] = nodeId;
|
|
||||||
|
|
||||||
newPendingEdges.push_back(std::make_pair((*oneNode)["id"], nodeId));
|
|
||||||
}
|
|
||||||
|
|
||||||
edgeIterator = edges.erase(edgeIterator);
|
|
||||||
}
|
|
||||||
int nextNewXedgeId = 1;
|
|
||||||
for (size_t i = 0; i < newPendingEdges.size(); i++) {
|
|
||||||
QString edgeId = QString("edgex%1").arg(nextNewXedgeId++);
|
|
||||||
std::map<QString, QString> *newEdge = &edges[edgeId];
|
|
||||||
(*newEdge)["id"] = edgeId;
|
|
||||||
(*newEdge)["from"] = newPendingEdges[i].first;
|
|
||||||
(*newEdge)["to"] = newPendingEdges[i].second;
|
|
||||||
|
|
||||||
edgeId = QString("edgex%1").arg(nextNewXedgeId++);
|
|
||||||
newEdge = &edges[edgeId];
|
|
||||||
(*newEdge)["id"] = edgeId;
|
|
||||||
(*newEdge)["from"] = nodes[newPendingEdges[i].first]["nextSidePair"];
|
|
||||||
(*newEdge)["to"] = nodes[newPendingEdges[i].second]["nextSidePair"];
|
|
||||||
}
|
|
||||||
for (edgeIterator = edges.begin(); edgeIterator != edges.end(); edgeIterator++) {
|
|
||||||
nodeLinkMap[edgeIterator->second["from"]].push_back(edgeIterator->second["to"]);
|
|
||||||
nodeLinkMap[edgeIterator->second["to"]].push_back(edgeIterator->second["from"]);
|
|
||||||
}
|
|
||||||
std::map<QString, std::map<QString, QString>>::iterator nodeIterator;
|
|
||||||
for (nodeIterator = nodes.begin(); nodeIterator != nodes.end(); nodeIterator++) {
|
|
||||||
nodeLinkMap[nodeIterator->first].push_back(nodeIterator->second["nextSidePair"]);
|
|
||||||
}
|
|
||||||
std::map<QString, int> nodeGroupMap;
|
|
||||||
std::vector<std::vector<QString>> nameGroups;
|
|
||||||
for (nodeIterator = nodes.begin(); nodeIterator != nodes.end(); nodeIterator++) {
|
|
||||||
if (nodeGroupMap.find(nodeIterator->first) != nodeGroupMap.end())
|
|
||||||
continue;
|
|
||||||
std::vector<QString> nameGroup;
|
|
||||||
joinNodeAndNeighborsToGroup(nodeLinkMap,
|
|
||||||
nodeGroupMap,
|
|
||||||
nodeIterator->first,
|
|
||||||
nameGroups.size(),
|
|
||||||
nameGroup);
|
|
||||||
for (size_t i = 0; i < nameGroup.size(); i++) {
|
|
||||||
nodeGroupMap[nameGroup[i]] = nameGroups.size();
|
|
||||||
}
|
|
||||||
nameGroups.push_back(nameGroup);
|
|
||||||
}
|
|
||||||
groups->resize(nameGroups.size());
|
|
||||||
for (edgeIterator = edges.begin(); edgeIterator != edges.end(); edgeIterator++) {
|
|
||||||
int groupIndex = nodeGroupMap[edgeIterator->second["from"]];
|
|
||||||
(*groups)[groupIndex].edges[edgeIterator->first] = edgeIterator->second;
|
|
||||||
}
|
|
||||||
for (nodeIterator = nodes.begin(); nodeIterator != nodes.end(); nodeIterator++) {
|
|
||||||
int groupIndex = nodeGroupMap[nodeIterator->first];
|
|
||||||
(*groups)[groupIndex].nodes[nodeIterator->first] = nodeIterator->second;
|
|
||||||
}
|
|
||||||
for (size_t i = 0; i < groups->size(); i++) {
|
|
||||||
(*groups)[i].canvas = canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const QRectF &SkeletonSnapshot::boundingBoxFront()
|
|
||||||
{
|
|
||||||
if (!m_boundingBoxResolved)
|
|
||||||
resolveBoundingBox();
|
|
||||||
return m_boundingBoxFront;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QRectF &SkeletonSnapshot::boundingBoxSide()
|
|
||||||
{
|
|
||||||
if (!m_boundingBoxResolved)
|
|
||||||
resolveBoundingBox();
|
|
||||||
return m_boundingBoxSide;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkeletonSnapshot::resolveBoundingBox()
|
|
||||||
{
|
|
||||||
if (m_boundingBoxResolved)
|
|
||||||
return;
|
|
||||||
m_boundingBoxResolved = true;
|
|
||||||
|
|
||||||
float left = -1;
|
|
||||||
float right = -1;
|
|
||||||
float top = -1;
|
|
||||||
float bottom = -1;
|
|
||||||
float zLeft = -1;
|
|
||||||
float zRight = -1;
|
|
||||||
std::map<QString, std::map<QString, QString>>::iterator nodeIterator;
|
|
||||||
for (nodeIterator = nodes.begin(); nodeIterator != nodes.end(); nodeIterator++) {
|
|
||||||
printf("loop node:%s color: %s\n", nodeIterator->first.toUtf8().constData(), nodeIterator->second["sideColorName"].toUtf8().constData());
|
|
||||||
}
|
|
||||||
for (nodeIterator = nodes.begin(); nodeIterator != nodes.end(); nodeIterator++) {
|
|
||||||
if ("red" != nodeIterator->second["sideColorName"])
|
|
||||||
continue;
|
|
||||||
float originX = nodeIterator->second["x"].toFloat();
|
|
||||||
float originY = nodeIterator->second["y"].toFloat();
|
|
||||||
float originZ = 0;
|
|
||||||
QString nextSidePairId = nodeIterator->second["nextSidePair"];
|
|
||||||
printf("nextSidePair: %s\n", nextSidePairId.toUtf8().constData());
|
|
||||||
std::map<QString, std::map<QString, QString>>::iterator findNextSidePair = nodes.find(nextSidePairId);
|
|
||||||
if (findNextSidePair != nodes.end()) {
|
|
||||||
originZ = findNextSidePair->second["x"].toFloat();
|
|
||||||
}
|
|
||||||
if (left < 0 || originX < left) {
|
|
||||||
left = originX;
|
|
||||||
}
|
|
||||||
if (top < 0 || originY < top) {
|
|
||||||
top = originY;
|
|
||||||
}
|
|
||||||
if (originX > right) {
|
|
||||||
right = originX;
|
|
||||||
}
|
|
||||||
if (originY > bottom) {
|
|
||||||
bottom = originY;
|
|
||||||
}
|
|
||||||
if (zLeft < 0 || originZ < zLeft) {
|
|
||||||
zLeft = originZ;
|
|
||||||
}
|
|
||||||
if (originZ > zRight) {
|
|
||||||
zRight = originZ;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
m_boundingBoxFront = QRectF(left, top, right - left, bottom - top);
|
|
||||||
m_boundingBoxSide = QRectF(zLeft, top, zRight - zLeft, bottom - top);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SkeletonSnapshot::rootNode()
|
|
||||||
{
|
|
||||||
if (!m_rootNodeResolved)
|
|
||||||
resolveRootNode();
|
|
||||||
return m_rootNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkeletonSnapshot::setRootNode(const QString &nodeName)
|
|
||||||
{
|
|
||||||
assert(!nodeName.isEmpty());
|
|
||||||
m_rootNode = nodeName;
|
|
||||||
m_rootNodeResolved = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString SkeletonSnapshot::findMaxNeighborNumberNode()
|
|
||||||
{
|
|
||||||
std::map<QString, std::map<QString, QString>>::iterator edgeIterator;
|
|
||||||
std::map<QString, int> nodeNeighborCountMap;
|
|
||||||
for (edgeIterator = edges.begin(); edgeIterator != edges.end(); edgeIterator++) {
|
|
||||||
if ("red" != nodes[edgeIterator->second["from"]]["sideColorName"])
|
|
||||||
continue;
|
|
||||||
nodeNeighborCountMap[edgeIterator->second["from"]]++;
|
|
||||||
nodeNeighborCountMap[edgeIterator->second["to"]]++;
|
|
||||||
}
|
|
||||||
if (nodeNeighborCountMap.size() == 0)
|
|
||||||
return "";
|
|
||||||
auto x = std::max_element(nodeNeighborCountMap.begin(), nodeNeighborCountMap.end(),
|
|
||||||
[](const std::pair<QString, int>& p1, const std::pair<QString, int>& p2) {
|
|
||||||
return p1.second < p2.second;
|
|
||||||
});
|
|
||||||
return x->first;
|
|
||||||
}
|
|
||||||
|
|
||||||
void SkeletonSnapshot::resolveRootNode()
|
|
||||||
{
|
|
||||||
if (m_rootNodeResolved)
|
|
||||||
return;
|
|
||||||
m_rootNodeResolved = true;
|
|
||||||
|
|
||||||
std::map<QString, std::map<QString, QString>>::iterator edgeIterator;
|
|
||||||
std::map<QString, int> nodeNeighborCountMap;
|
|
||||||
for (edgeIterator = edges.begin(); edgeIterator != edges.end(); edgeIterator++) {
|
|
||||||
if ("red" != nodes[edgeIterator->second["from"]]["sideColorName"])
|
|
||||||
continue;
|
|
||||||
nodeNeighborCountMap[edgeIterator->second["from"]]++;
|
|
||||||
nodeNeighborCountMap[edgeIterator->second["to"]]++;
|
|
||||||
}
|
|
||||||
std::map<QString, std::map<QString, QString>>::iterator nodeIterator;
|
|
||||||
|
|
||||||
// First try to select the node with more than 2 neighbors and have the largest radius.
|
|
||||||
float maxRadius = 0;
|
|
||||||
m_rootNode = "";
|
|
||||||
for (nodeIterator = nodes.begin(); nodeIterator != nodes.end(); nodeIterator++) {
|
|
||||||
if ("red" != nodeIterator->second["sideColorName"])
|
|
||||||
continue;
|
|
||||||
if ("true" == nodeIterator->second["isRoot"]) {
|
|
||||||
m_rootNode = nodeIterator->first;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (nodeNeighborCountMap[nodeIterator->first] < 3)
|
|
||||||
continue;
|
|
||||||
float radius = nodeIterator->second["radius"].toFloat();
|
|
||||||
if (radius > maxRadius) {
|
|
||||||
maxRadius = radius;
|
|
||||||
m_rootNode = nodeIterator->first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_rootNode.isEmpty())
|
|
||||||
m_rootNode = findMaxNeighborNumberNode();
|
|
||||||
|
|
||||||
if (m_rootNode.isEmpty()) {
|
|
||||||
nodeIterator = nodes.begin();
|
|
||||||
if (nodeIterator != nodes.end()) {
|
|
||||||
m_rootNode = nodeIterator->first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -13,26 +13,7 @@ public:
|
||||||
std::map<QString, std::map<QString, QString>> nodes;
|
std::map<QString, std::map<QString, QString>> nodes;
|
||||||
std::map<QString, std::map<QString, QString>> edges;
|
std::map<QString, std::map<QString, QString>> edges;
|
||||||
std::map<QString, std::map<QString, QString>> parts;
|
std::map<QString, std::map<QString, QString>> parts;
|
||||||
std::map<QString, std::map<QString, QString>> mirrors;
|
|
||||||
std::map<QString, std::map<QString, QString>> trivialMarkers;
|
|
||||||
std::vector<QString> partIdList;
|
std::vector<QString> partIdList;
|
||||||
public:
|
|
||||||
SkeletonSnapshot();
|
|
||||||
void splitByConnectivity(std::vector<SkeletonSnapshot> *groups);
|
|
||||||
QString rootNode();
|
|
||||||
void setRootNode(const QString &nodeName);
|
|
||||||
const QRectF &boundingBoxFront();
|
|
||||||
const QRectF &boundingBoxSide();
|
|
||||||
private:
|
|
||||||
QString m_rootNode;
|
|
||||||
QRectF m_boundingBoxFront;
|
|
||||||
QRectF m_boundingBoxSide;
|
|
||||||
bool m_boundingBoxResolved;
|
|
||||||
bool m_rootNodeResolved;
|
|
||||||
private:
|
|
||||||
void resolveBoundingBox();
|
|
||||||
void resolveRootNode();
|
|
||||||
QString findMaxNeighborNumberNode();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue