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
|
||||
--------
|
||||
```
|
||||
$ cd thirdparty/carve-1.4.0/build
|
||||
$ cmake ../
|
||||
$ make && make install
|
||||
|
||||
$ 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
|
||||
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)
|
||||
|
||||
INCLUDEPATH += src
|
||||
|
@ -30,6 +45,9 @@ HEADERS += src/skeletongraphicswidget.h
|
|||
SOURCES += src/skeletonpartlistwidget.cpp
|
||||
HEADERS += src/skeletonpartlistwidget.h
|
||||
|
||||
SOURCES += src/aboutwidget.cpp
|
||||
HEADERS += src/aboutwidget.h
|
||||
|
||||
SOURCES += src/meshgenerator.cpp
|
||||
HEADERS += src/meshgenerator.h
|
||||
|
||||
|
|
Binary file not shown.
|
@ -1,5 +1,6 @@
|
|||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource>
|
||||
<file>resources/dust3d_jezzasoft.png</file>
|
||||
<file>resources/dust3d_vertical.png</file>
|
||||
<file>ACKNOWLEDGEMENTS.html</file>
|
||||
</qresource>
|
||||
</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");
|
||||
m_browserDialog = new LogBrowserDialog;
|
||||
connect(this, SIGNAL(sendMessage(QtMsgType,QString)), m_browserDialog, SLOT(outputMessage(QtMsgType,QString)), Qt::QueuedConnection);
|
||||
m_browserDialog->show();
|
||||
}
|
||||
|
||||
LogBrowser::~LogBrowser()
|
||||
|
@ -17,6 +16,23 @@ LogBrowser::~LogBrowser()
|
|||
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)
|
||||
{
|
||||
printf("%s\n", msg.toUtf8().constData());
|
||||
|
|
|
@ -14,6 +14,9 @@ public:
|
|||
|
||||
public slots:
|
||||
void outputMessage(QtMsgType type, const QString &msg);
|
||||
void showDialog();
|
||||
void hideDialog();
|
||||
bool isDialogVisible();
|
||||
|
||||
signals:
|
||||
void sendMessage(QtMsgType type, const QString &msg);
|
||||
|
@ -22,4 +25,4 @@ private:
|
|||
LogBrowserDialog *m_browserDialog;
|
||||
};
|
||||
|
||||
#endif // LOGBROWSER_H
|
||||
#endif // LOGBROWSER_H
|
||||
|
|
|
@ -38,6 +38,8 @@ LogBrowserDialog::LogBrowserDialog(QWidget *parent) :
|
|||
connect(m_saveButton, SIGNAL(clicked()), this, SLOT(save()));
|
||||
|
||||
resize(400, 300);
|
||||
|
||||
hide();
|
||||
}
|
||||
|
||||
|
||||
|
@ -100,15 +102,8 @@ void LogBrowserDialog::save()
|
|||
|
||||
void LogBrowserDialog::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
QMessageBox::StandardButton answer = QMessageBox::question(this,
|
||||
tr("Close Log Browser?"),
|
||||
tr("Do you really want to close the log browser?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if (answer == QMessageBox::Yes)
|
||||
e->accept();
|
||||
else
|
||||
e->ignore();
|
||||
e->ignore();
|
||||
hide();
|
||||
}
|
||||
|
||||
void LogBrowserDialog::keyPressEvent(QKeyEvent *e)
|
||||
|
|
17
src/main.cpp
17
src/main.cpp
|
@ -2,21 +2,11 @@
|
|||
#include <QDesktopWidget>
|
||||
#include <QStyleFactory>
|
||||
#include <QFontDatabase>
|
||||
#include <QPointer>
|
||||
#include <QDebug>
|
||||
#include <QtGlobal>
|
||||
#include "logbrowser.h"
|
||||
#include "skeletondocumentwindow.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)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
@ -49,11 +39,8 @@ int main(int argc, char ** argv)
|
|||
font.setPixelSize(9);
|
||||
font.setBold(false);
|
||||
QApplication::setFont(font);
|
||||
|
||||
g_logBrowser = new LogBrowser;
|
||||
qInstallMessageHandler(&outputMessage);
|
||||
|
||||
SkeletonDocumentWindow mainWindow;
|
||||
mainWindow.showMaximized();
|
||||
SkeletonDocumentWindow::createDocumentWindow();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
|
|
@ -37,6 +37,21 @@ ModelWidget::ModelWidget(QWidget *parent)
|
|||
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)
|
||||
{
|
||||
m_graphicsFunctions = graphicsFunctions;
|
||||
|
|
|
@ -47,6 +47,12 @@ protected:
|
|||
void mouseMoveEvent(QMouseEvent *event) override;
|
||||
void wheelEvent(QWheelEvent *event) override;
|
||||
void mouseReleaseEvent(QMouseEvent *event) override;
|
||||
|
||||
public:
|
||||
int xRot();
|
||||
int yRot();
|
||||
int zRot();
|
||||
|
||||
private:
|
||||
int m_xRot;
|
||||
int m_yRot;
|
||||
|
|
|
@ -26,6 +26,7 @@ SkeletonDocument::~SkeletonDocument()
|
|||
|
||||
void SkeletonDocument::uiReady()
|
||||
{
|
||||
qDebug() << "uiReady";
|
||||
emit editModeChanged();
|
||||
}
|
||||
|
||||
|
@ -615,24 +616,19 @@ void SkeletonDocument::addFromSnapshot(const SkeletonSnapshot &snapshot)
|
|||
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();
|
||||
edgeMap.clear();
|
||||
partMap.clear();
|
||||
partIds.clear();
|
||||
emit partListChanged();
|
||||
|
||||
emit cleanup();
|
||||
emit skeletonChanged();
|
||||
}
|
||||
|
||||
void SkeletonDocument::fromSnapshot(const SkeletonSnapshot &snapshot)
|
||||
{
|
||||
reset();
|
||||
addFromSnapshot(snapshot);
|
||||
}
|
||||
|
||||
|
|
|
@ -123,6 +123,7 @@ signals:
|
|||
void partLockStateChanged(QUuid partId);
|
||||
void partVisibleStateChanged(QUuid partId);
|
||||
void partSubdivStateChanged(QUuid partId);
|
||||
void cleanup();
|
||||
public:
|
||||
SkeletonDocument();
|
||||
~SkeletonDocument();
|
||||
|
@ -168,6 +169,7 @@ public slots:
|
|||
void paste();
|
||||
void batchChangeBegin();
|
||||
void batchChangeEnd();
|
||||
void reset();
|
||||
private:
|
||||
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());
|
||||
|
|
|
@ -6,15 +6,74 @@
|
|||
#include <QPushButton>
|
||||
#include <QFileDialog>
|
||||
#include <QTabWidget>
|
||||
#include <QBuffer>
|
||||
#include <QMessageBox>
|
||||
#include <QTimer>
|
||||
#include <QMenuBar>
|
||||
#include <QPointer>
|
||||
#include <QApplication>
|
||||
#include <set>
|
||||
#include <QDesktopServices>
|
||||
#include "skeletondocumentwindow.h"
|
||||
#include "skeletongraphicswidget.h"
|
||||
#include "skeletonpartlistwidget.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() :
|
||||
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;
|
||||
|
||||
QVBoxLayout *toolButtonLayout = new QVBoxLayout;
|
||||
|
@ -47,13 +106,13 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
toolButtonLayout->addWidget(zoomInButton);
|
||||
toolButtonLayout->addWidget(zoomOutButton);
|
||||
|
||||
QLabel *dust3dJezzasoftLabel = new QLabel;
|
||||
QImage dust3dJezzasoftImage;
|
||||
dust3dJezzasoftImage.load(":/resources/dust3d_jezzasoft.png");
|
||||
dust3dJezzasoftLabel->setPixmap(QPixmap::fromImage(dust3dJezzasoftImage));
|
||||
QLabel *verticalLogoLabel = new QLabel;
|
||||
QImage verticalLogoImage;
|
||||
verticalLogoImage.load(":/resources/dust3d_vertical.png");
|
||||
verticalLogoLabel->setPixmap(QPixmap::fromImage(verticalLogoImage));
|
||||
|
||||
QHBoxLayout *logoLayout = new QHBoxLayout;
|
||||
logoLayout->addWidget(dust3dJezzasoftLabel);
|
||||
logoLayout->addWidget(verticalLogoLabel);
|
||||
logoLayout->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
QVBoxLayout *mainLeftLayout = new QVBoxLayout;
|
||||
|
@ -65,9 +124,9 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
mainLeftLayout->addSpacing(10);
|
||||
|
||||
SkeletonGraphicsWidget *graphicsWidget = new SkeletonGraphicsWidget(m_document);
|
||||
m_graphicsWidget = graphicsWidget;
|
||||
|
||||
SkeletonGraphicsContainerWidget *containerWidget = new SkeletonGraphicsContainerWidget;
|
||||
|
||||
containerWidget->setGraphicsWidget(graphicsWidget);
|
||||
QGridLayout *containerLayout = new QGridLayout;
|
||||
containerLayout->setSpacing(0);
|
||||
|
@ -103,6 +162,147 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
setCentralWidget(centralWidget);
|
||||
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,
|
||||
graphicsWidget, &SkeletonGraphicsWidget::canvasResized);
|
||||
|
||||
|
@ -146,10 +346,14 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
connect(graphicsWidget, &SkeletonGraphicsWidget::undo, m_document, &SkeletonDocument::undo);
|
||||
connect(graphicsWidget, &SkeletonGraphicsWidget::redo, m_document, &SkeletonDocument::redo);
|
||||
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::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::nodeRemoved, graphicsWidget, &SkeletonGraphicsWidget::nodeRemoved);
|
||||
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::nodeOriginChanged, graphicsWidget, &SkeletonGraphicsWidget::nodeOriginChanged);
|
||||
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::partPreviewChanged, partListWidget, &SkeletonPartListWidget::partPreviewChanged);
|
||||
connect(m_document, &SkeletonDocument::partLockStateChanged, partListWidget, &SkeletonPartListWidget::partLockStateChanged);
|
||||
connect(m_document, &SkeletonDocument::partVisibleStateChanged, partListWidget, &SkeletonPartListWidget::partVisibleStateChanged);
|
||||
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);
|
||||
|
||||
|
@ -174,14 +380,118 @@ SkeletonDocumentWindow::SkeletonDocumentWindow() :
|
|||
m_modelWidget->setCursor(graphicsWidget->cursor());
|
||||
});
|
||||
|
||||
connect(this, &SkeletonDocumentWindow::initialized, m_document, [=]() {
|
||||
m_document->saveSnapshot();
|
||||
graphicsWidget->setFocus();
|
||||
});
|
||||
connect(m_document, &SkeletonDocument::skeletonChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||
connect(m_document, &SkeletonDocument::turnaroundChanged, this, &SkeletonDocumentWindow::documentChanged);
|
||||
|
||||
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)
|
||||
{
|
||||
button->setFont(Theme::awesome()->font(Theme::toolIconFontSize));
|
||||
|
@ -191,6 +501,7 @@ void SkeletonDocumentWindow::initButton(QPushButton *button)
|
|||
|
||||
SkeletonDocumentWindow::~SkeletonDocumentWindow()
|
||||
{
|
||||
g_documentWindows.erase(this);
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::showEvent(QShowEvent *event)
|
||||
|
@ -198,14 +509,16 @@ void SkeletonDocumentWindow::showEvent(QShowEvent *event)
|
|||
QMainWindow::showEvent(event);
|
||||
if (m_firstShow) {
|
||||
m_firstShow = false;
|
||||
updateTitle();
|
||||
m_document->saveSnapshot();
|
||||
m_graphicsWidget->setFocus();
|
||||
emit initialized();
|
||||
}
|
||||
}
|
||||
|
||||
void SkeletonDocumentWindow::changeTurnaround()
|
||||
{
|
||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Open Turnaround Reference Image"),
|
||||
QString(),
|
||||
QString fileName = QFileDialog::getOpenFileName(this, QString(), QString(),
|
||||
tr("Image Files (*.png *.jpg *.bmp)")).trimmed();
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
@ -214,3 +527,102 @@ void SkeletonDocumentWindow::changeTurnaround()
|
|||
return;
|
||||
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 <QShowEvent>
|
||||
#include <QPushButton>
|
||||
#include <QString>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
#include <QTextBrowser>
|
||||
#include "skeletondocument.h"
|
||||
#include "modelwidget.h"
|
||||
|
||||
class SkeletonGraphicsWidget;
|
||||
|
||||
class SkeletonDocumentWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -14,16 +20,72 @@ signals:
|
|||
public:
|
||||
SkeletonDocumentWindow();
|
||||
~SkeletonDocumentWindow();
|
||||
static SkeletonDocumentWindow *createDocumentWindow();
|
||||
static void showAcknowlegements();
|
||||
static void showAbout();
|
||||
protected:
|
||||
void showEvent(QShowEvent *event);
|
||||
void closeEvent(QCloseEvent *event);
|
||||
public slots:
|
||||
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:
|
||||
void initButton(QPushButton *button);
|
||||
void setCurrentFilename(const QString &filename);
|
||||
void updateTitle();
|
||||
private:
|
||||
SkeletonDocument *m_document;
|
||||
bool m_firstShow;
|
||||
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
|
||||
|
|
|
@ -46,10 +46,12 @@ SkeletonGraphicsWidget::SkeletonGraphicsWidget(const SkeletonDocument *document)
|
|||
|
||||
m_cursorNodeItem = new SkeletonGraphicsNodeItem();
|
||||
m_cursorNodeItem->hide();
|
||||
m_cursorNodeItem->setData(0, "cursorNode");
|
||||
scene()->addItem(m_cursorNodeItem);
|
||||
|
||||
m_cursorEdgeItem = new SkeletonGraphicsEdgeItem();
|
||||
m_cursorEdgeItem->hide();
|
||||
m_cursorEdgeItem->setData(0, "cursorEdge");
|
||||
scene()->addItem(m_cursorEdgeItem);
|
||||
|
||||
m_selectionItem = new SkeletonGraphicsSelectionItem();
|
||||
|
@ -77,7 +79,7 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
|
||||
QMenu contextMenu(this);
|
||||
|
||||
QAction addAction(tr("Add.."), this);
|
||||
QAction addAction(tr("Add..."), this);
|
||||
connect(&addAction, &QAction::triggered, [=]() {
|
||||
emit setEditMode(SkeletonDocumentEditMode::Add);
|
||||
});
|
||||
|
@ -96,19 +98,19 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
}
|
||||
|
||||
QAction deleteAction(tr("Delete"), this);
|
||||
if (!m_rangeSelectionSet.empty()) {
|
||||
if (hasSelection()) {
|
||||
connect(&deleteAction, &QAction::triggered, this, &SkeletonGraphicsWidget::deleteSelected);
|
||||
contextMenu.addAction(&deleteAction);
|
||||
}
|
||||
|
||||
QAction cutAction(tr("Cut"), this);
|
||||
if (!m_rangeSelectionSet.empty()) {
|
||||
if (hasSelection()) {
|
||||
connect(&cutAction, &QAction::triggered, this, &SkeletonGraphicsWidget::cut);
|
||||
contextMenu.addAction(&cutAction);
|
||||
}
|
||||
|
||||
QAction copyAction(tr("Copy"), this);
|
||||
if (!m_rangeSelectionSet.empty()) {
|
||||
if (hasSelection()) {
|
||||
connect(©Action, &QAction::triggered, this, &SkeletonGraphicsWidget::copy);
|
||||
contextMenu.addAction(©Action);
|
||||
}
|
||||
|
@ -120,38 +122,44 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &pos)
|
|||
}
|
||||
|
||||
QAction flipHorizontallyAction(tr("H Flip"), this);
|
||||
if (!m_rangeSelectionSet.empty() && !isSingleNodeSelected()) {
|
||||
if (hasMultipleSelection()) {
|
||||
connect(&flipHorizontallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipHorizontally);
|
||||
contextMenu.addAction(&flipHorizontallyAction);
|
||||
}
|
||||
|
||||
QAction flipVerticallyAction(tr("V Flip"), this);
|
||||
if (!m_rangeSelectionSet.empty() && !isSingleNodeSelected()) {
|
||||
if (hasMultipleSelection()) {
|
||||
connect(&flipVerticallyAction, &QAction::triggered, this, &SkeletonGraphicsWidget::flipVertically);
|
||||
contextMenu.addAction(&flipVerticallyAction);
|
||||
}
|
||||
|
||||
QAction selectAllAction(tr("Select All"), this);
|
||||
if (!nodeItemMap.empty()) {
|
||||
if (hasItems()) {
|
||||
connect(&selectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectAll);
|
||||
contextMenu.addAction(&selectAllAction);
|
||||
}
|
||||
|
||||
QAction selectPartAllAction(tr("Select Part"), this);
|
||||
if (!nodeItemMap.empty()) {
|
||||
if (hasItems()) {
|
||||
connect(&selectPartAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::selectPartAll);
|
||||
contextMenu.addAction(&selectPartAllAction);
|
||||
}
|
||||
|
||||
QAction unselectAllAction(tr("Unselect All"), this);
|
||||
if (!m_rangeSelectionSet.empty()) {
|
||||
if (hasSelection()) {
|
||||
connect(&unselectAllAction, &QAction::triggered, this, &SkeletonGraphicsWidget::unselectAll);
|
||||
contextMenu.addAction(&unselectAllAction);
|
||||
}
|
||||
|
||||
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, [=]() {
|
||||
emit changeTurnaround();
|
||||
});
|
||||
|
@ -160,6 +168,21 @@ void SkeletonGraphicsWidget::showContextMenu(const QPoint &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()
|
||||
{
|
||||
for (auto nodeItemIt = nodeItemMap.begin(); nodeItemIt != nodeItemMap.end(); nodeItemIt++) {
|
||||
|
@ -669,6 +692,10 @@ bool SkeletonGraphicsWidget::mouseDoubleClick(QMouseEvent *event)
|
|||
selectPartAll();
|
||||
return true;
|
||||
}
|
||||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit open();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -734,6 +761,10 @@ bool SkeletonGraphicsWidget::keyPress(QKeyEvent *event)
|
|||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit paste();
|
||||
}
|
||||
} else if (event->key() == Qt::Key_S) {
|
||||
if (QGuiApplication::queryKeyboardModifiers().testFlag(Qt::ControlModifier)) {
|
||||
emit save();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -1153,6 +1184,33 @@ void SkeletonGraphicsWidget::copy()
|
|||
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()
|
||||
{
|
||||
if (m_rangeSelectionSet.size() != 1)
|
||||
|
|
|
@ -266,6 +266,9 @@ signals:
|
|||
void changeTurnaround();
|
||||
void batchChangeBegin();
|
||||
void batchChangeEnd();
|
||||
void save();
|
||||
void open();
|
||||
void exportResult();
|
||||
public:
|
||||
SkeletonGraphicsWidget(const SkeletonDocument *document);
|
||||
std::map<QUuid, std::pair<SkeletonGraphicsNodeItem *, SkeletonGraphicsNodeItem *>> nodeItemMap;
|
||||
|
@ -281,6 +284,9 @@ public:
|
|||
void readMergedSkeletonNodeSetFromRangeSelection(std::set<SkeletonGraphicsNodeItem *> *nodeItemSet);
|
||||
void readSkeletonNodeAndEdgeIdSetFromRangeSelection(std::set<QUuid> *nodeIdSet, std::set<QUuid> *edgeIdSet);
|
||||
bool readSkeletonNodeAndAnyEdgeOfNodeFromRangeSelection(SkeletonGraphicsNodeItem **nodeItem, SkeletonGraphicsEdgeItem **edgeItem);
|
||||
bool hasSelection();
|
||||
bool hasItems();
|
||||
bool hasMultipleSelection();
|
||||
protected:
|
||||
void mouseMoveEvent(QMouseEvent *event);
|
||||
void wheelEvent(QWheelEvent *event);
|
||||
|
@ -310,6 +316,7 @@ public slots:
|
|||
void copy();
|
||||
void flipHorizontally();
|
||||
void flipVertically();
|
||||
void removeAllContent();
|
||||
private slots:
|
||||
void turnaroundImageReady();
|
||||
private:
|
||||
|
|
|
@ -1,282 +1,2 @@
|
|||
#include "skeletonsnapshot.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>> edges;
|
||||
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;
|
||||
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
|
||||
|
|
Loading…
Reference in New Issue