PartDisign/WorkflowManager: initial implementation

This commit is contained in:
Alexander Golubev
2015-08-04 00:36:25 +03:00
committed by Stefan Tröger
parent eec03e20cc
commit 9e47fea5ab
5 changed files with 406 additions and 13 deletions

View File

@@ -233,6 +233,8 @@ SET(PartDesignGuiModule_SRCS
Utils.h
Workbench.cpp
Workbench.h
WorkflowManager.cpp
WorkflowManager.h
)
SOURCE_GROUP("Module" FILES ${PartDesignGuiModule_SRCS})

View File

@@ -39,6 +39,7 @@
#include <Mod/PartDesign/App/Feature.h>
#include "Utils.h"
#include "WorkflowManager.h"
@@ -61,6 +62,9 @@ CmdPartDesignPart::CmdPartDesignPart()
void CmdPartDesignPart::activated(int iMsg)
{
if ( PartDesignGui::assureModernWorkflow( getDocument() ) )
return;
openCommand("Add a part");
std::string FeatName = getUniqueObjectName("Part");
@@ -76,7 +80,7 @@ void CmdPartDesignPart::activated(int iMsg)
bool CmdPartDesignPart::isActive(void)
{
return hasActiveDocument();
return hasActiveDocument() && !PartDesignGui::isLegacyWorkflow ( getDocument () );
}
//===========================================================================
@@ -98,6 +102,8 @@ CmdPartDesignBody::CmdPartDesignBody()
void CmdPartDesignBody::activated(int iMsg)
{
if ( PartDesignGui::assureModernWorkflow( getDocument() ) )
return;
std::vector<App::DocumentObject*> features =
getSelection().getObjectsOfType(Part::Feature::getClassTypeId());
App::DocumentObject* baseFeature = nullptr;
@@ -154,7 +160,40 @@ void CmdPartDesignBody::activated(int iMsg)
bool CmdPartDesignBody::isActive(void)
{
return hasActiveDocument();
return hasActiveDocument() && !PartDesignGui::isLegacyWorkflow ( getDocument () );
}
//===========================================================================
// PartDesign_Migrate
//===========================================================================
DEF_STD_CMD_A(CmdPartDesignMigrate);
CmdPartDesignMigrate::CmdPartDesignMigrate()
: Command("PartDesign_Migrate")
{
sAppModule = "PartDesign";
sGroup = QT_TR_NOOP("PartDesign");
sMenuText = QT_TR_NOOP("Migrate");
sToolTipText = QT_TR_NOOP("Migrate document to the new workflow");
sWhatsThis = sToolTipText;
sStatusTip = sToolTipText;
}
void CmdPartDesignMigrate::activated(int iMsg)
{
App::Document *doc = getDocument();
// TODO make a proper implementation
QMessageBox::warning(Gui::getMainWindow(), QObject::tr("Not implemented yet"),
QObject::tr("The migration not implemented yet, just force-switching to the new workflow.\n"
"Previous workflow was: %1").arg(int(
PartDesignGui::WorkflowManager::instance()->determinWorkflow( doc ) )));
PartDesignGui::WorkflowManager::instance()->forceWorkflow(doc, PartDesignGui::Workflow::Modern);
}
bool CmdPartDesignMigrate::isActive(void)
{
return hasActiveDocument() && !PartDesignGui::isLegacyWorkflow ( getDocument () );
}
//===========================================================================
@@ -290,10 +329,7 @@ void CmdPartDesignDuplicateSelection::activated(int iMsg) {
bool CmdPartDesignDuplicateSelection::isActive(void)
{
if (getActiveGuiDocument())
return true;
else
return false;
return hasActiveDocument();
}
//===========================================================================
@@ -387,7 +423,7 @@ void CmdPartDesignMoveFeature::activated(int iMsg)
} catch (Base::Exception &) {
QMessageBox::warning( Gui::getMainWindow(), QObject::tr("Sketch plane cannot be migrated"),
QObject::tr("Please edit '%1' and redefine it to use a Base or Datum plane as the sketch plane.").
arg( QString::fromAscii( sketch->getNameInDocument() ) ) );
arg( QString::fromAscii( sketch->Label.getValue () ) ) );
}
}
}
@@ -397,6 +433,7 @@ void CmdPartDesignMoveFeature::activated(int iMsg)
bool CmdPartDesignMoveFeature::isActive(void)
{
return hasActiveDocument () && !PartDesignGui::isLegacyWorkflow ( getDocument () );
return hasActiveDocument ();
}
@@ -494,7 +531,7 @@ void CmdPartDesignMoveFeatureInTree::activated(int iMsg)
bool CmdPartDesignMoveFeatureInTree::isActive(void)
{
return hasActiveDocument ();
return hasActiveDocument () && !PartDesignGui::isLegacyWorkflow ( getDocument () );
}
@@ -508,6 +545,7 @@ void CreatePartDesignBodyCommands(void)
rcCmdMgr.addCommand(new CmdPartDesignPart());
rcCmdMgr.addCommand(new CmdPartDesignBody());
rcCmdMgr.addCommand(new CmdPartDesignMigrate());
rcCmdMgr.addCommand(new CmdPartDesignMoveTip());
rcCmdMgr.addCommand(new CmdPartDesignDuplicateSelection());

View File

@@ -44,6 +44,8 @@
#include "Workbench.h"
#include "WorkflowManager.h"
using namespace PartDesignGui;
#if 0 // needed for Qt's lupdate utility
@@ -56,12 +58,11 @@ using namespace PartDesignGui;
/// @namespace PartDesignGui @class Workbench
TYPESYSTEM_SOURCE(PartDesignGui::Workbench, Gui::StdWorkbench)
Workbench::Workbench()
{
Workbench::Workbench() {
}
Workbench::~Workbench()
{
Workbench::~Workbench() {
WorkflowManager::destruct();
}
// Commented out due to later to be moves and/or generall rewrighted from scratch (Fat-Zer 2015-08-08)
@@ -370,6 +371,7 @@ void Workbench::activated()
{
Gui::Workbench::activated();
WorkflowManager::init();
std::vector<Gui::TaskView::TaskWatcher*> Watcher;
@@ -630,7 +632,9 @@ Gui::MenuItem* Workbench::setupMenuBar() const
<< "PartDesign_Boolean"
<< "Separator"
//<< "PartDesign_Hole"
<< "PartDesign_InvoluteGear";
<< "PartDesign_InvoluteGear"
<< "Separator"
<< "PartDesign_Migrate";
// For 0.13 a couple of python packages like numpy, matplotlib and others
// are not deployed with the installer on Windows. Thus, the WizardShaft is

View File

@@ -0,0 +1,218 @@
/***************************************************************************
* Copyright (C) 2015 Alexander Golubev (Fat-Zer) <fatzer2@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
#include <vector>
#include <list>
#include <set>
#include <boost/bind.hpp>
#include <QMessageBox>
#include <QPushButton>
#endif
#include <Base/Exception.h>
#include <App/Application.h>
#include <App/Document.h>
#include <Gui/MainWindow.h>
#include <Gui/Command.h>
#include <Gui/Application.h>
#include <Mod/PartDesign/App/Body.h>
#include <Mod/PartDesign/App/Feature.h>
#include "WorkflowManager.h"
using namespace PartDesignGui;
WorkflowManager * WorkflowManager::_instance = nullptr;
WorkflowManager::WorkflowManager() {
// Fill the map with already opened documents
for ( auto doc : App::GetApplication().getDocuments() ) {
slotFinishRestoreDocument ( *doc );
}
connectNewDocument = App::GetApplication().signalNewDocument.connect(
boost::bind( &WorkflowManager::slotNewDocument, this, _1 ) );
connectFinishRestoreDocument = App::GetApplication().signalFinishRestoreDocument.connect(
boost::bind( &WorkflowManager::slotFinishRestoreDocument, this, _1 ) );
connectDeleteDocument = App::GetApplication().signalDeleteDocument.connect(
boost::bind( &WorkflowManager::slotDeleteDocument, this, _1 ) );
}
WorkflowManager::~WorkflowManager() {
// won't they will be disconnected on destruction?
connectNewDocument.disconnect ();
connectFinishRestoreDocument.disconnect ();
}
// Those destruction/construction is not really needed and could be done in the instance()
// but to make things a bit more cleare better to keep them around.
void WorkflowManager::init() {
if (_instance) {
throw Base::Exception( "Trying to init the workflow manager second time." );
}
_instance = new WorkflowManager();
}
WorkflowManager *WorkflowManager::instance() {
if (!_instance) {
throw Base::Exception( "Trying to instance the WorkflowManager manager before init() was called." );
}
return _instance;
}
void WorkflowManager::destruct() {
if (_instance) {
delete _instance;
_instance = nullptr;
}
}
void WorkflowManager::slotNewDocument( const App::Document &doc ) {
// new document always uses new workflow
dwMap[&doc] = Workflow::Modern;
}
void WorkflowManager::slotFinishRestoreDocument( const App::Document &doc ) {
Workflow wf = guessWorkflow (&doc);
// Mark document as undetermined if the guessed workflow is not new
if( wf != Workflow::Modern ) {
wf = Workflow::Undetermined;
}
dwMap[&doc] = wf;
}
void WorkflowManager::slotDeleteDocument( const App::Document &doc ) {
dwMap.erase(&doc);
}
Workflow WorkflowManager::getWorkflowForDocument( App::Document *doc) {
assert (doc);
auto it = dwMap.find(doc);
if ( it!=dwMap.end() ) {
return it->second;
} else {
// We haven't yet checked the file workflow
// May happen if e.g. file not compleatly loaded yet
return Workflow::Undetermined;
}
}
Workflow WorkflowManager::determinWorkflow( App::Document *doc) {
Workflow rv = getWorkflowForDocument (doc);
if (rv != Workflow::Undetermined) {
// Return if workflow is known
return rv;
}
// Guess the workflow again
rv = guessWorkflow (doc);
if (rv != Workflow::Modern) {
QMessageBox msgBox;
if ( rv == Workflow::Legacy ) { // legacy messages
msgBox.setText( QObject::tr( "The document \"%1\" you are editing was design with old version of "
"PartDesign workbench." ).arg( QString::fromStdString ( doc->getName()) ) );
msgBox.setInformativeText (
QObject::tr( "Do you want to migrate in order to use modern PartDesign features?" ) );
} else { // The document is already in the middle of migration
msgBox.setText( QObject::tr( "The document \"%1\" seems to be either in the middle of"
" the migration process from legacy PartDesign or have a slightly broken structure."
).arg( QString::fromStdString ( doc->getName()) ) );
msgBox.setInformativeText (
QObject::tr( "Do you want to make the migration automatically?" ) );
}
msgBox.setDetailedText( QObject::tr( "Note If you choose to migrate you won't be able to edit"
" the file wtih old FreeCAD versions.\n"
"If you refuse to migrate you won't be able to use new PartDesign features"
" like Bodies and Parts. As a result you also won't be able to use your parts"
" in the assembly workbench.\n"
"Although you will be able to migrate any moment later with 'Part Design->Migrate'." ) );
msgBox.setIcon( QMessageBox::Question );
QPushButton * yesBtn = msgBox.addButton ( QMessageBox::Yes );
QPushButton * manuallyBtn = msgBox.addButton (
QObject::tr ( "Migrate manually" ), QMessageBox::YesRole );
// If it is already a document in the middle of the migration the user shouldn't refuse to migrate
if ( rv != Workflow::Undetermined ) {
msgBox.addButton ( QMessageBox::No );
}
msgBox.setDefaultButton ( yesBtn );
// TODO Add some description of manual migration mode (2015-08-09, Fat-Zer)
msgBox.exec();
if ( msgBox.clickedButton() == yesBtn ) {
// TODO Assure that this will actually work (2015-08-02, Fat-Zer)
Gui::Application::Instance->commandManager().runCommandByName("PartDesign_Migrate");
rv = Workflow::Modern;
} else if ( msgBox.clickedButton() == manuallyBtn ) {
rv = Workflow::Modern;
} else {
rv = Workflow::Legacy;
}
}
// Actually set the result in our map
dwMap[ doc ] = rv;
return rv;
}
void WorkflowManager::forceWorkflow( const App::Document *doc, Workflow wf) {
dwMap[ doc ] = wf;
}
Workflow WorkflowManager::guessWorkflow(const App::Document *doc) {
// Retrive bodies of the document
auto features = doc->getObjectsOfType( PartDesign::Feature::getClassTypeId() );
if( features.empty() ) {
// a new file should be done in the new workflow
return Workflow::Modern;
} else {
auto bodies = doc->getObjectsOfType( PartDesign::Body::getClassTypeId() );
if (bodies.empty()) {
// If there are no bodies workflow is legacy
return Workflow::Legacy;
} else {
bool features_without_bodies = false;
for( auto feat: features ) {
if( !PartDesign::Body::findBodyOf( feat ) ) {
features_without_bodies = true;
break;
}
}
// if there are features not belonging to any body itmeans that migration was incomplete, otherwice it's Modern
return features_without_bodies ? Workflow::Undetermined : Workflow::Modern;
}
}
}

View File

@@ -0,0 +1,131 @@
/***************************************************************************
* Copyright (C) 2015 Alexander Golubev (Fat-Zer) <fatzer2@gmail.com> *
* *
* This file is part of the FreeCAD CAx development system. *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public *
* License along with this library; see the file COPYING.LIB. If not, *
* write to the Free Software Foundation, Inc., 59 Temple Place, *
* Suite 330, Boston, MA 02111-1307, USA *
* *
***************************************************************************/
#ifndef WORKFLOWMANAGER_H_PB7A5GCM
#define WORKFLOWMANAGER_H_PB7A5GCM
#include <boost/signals.hpp>
#include <map>
namespace App {
class Document;
}
namespace PartDesignGui {
/**
* Defines allowded tool set provided by the workbench
* Legacy mode provides a free PartDesign features but forbids bodies and parts
*/
enum class Workflow {
Undetermined = 0, ///< No workflow was choosen yet
Legacy = 1<<0, ///< Old-style workflow with free features and no bodies
Modern = 1<<1, ///< New-style workflow with bodies, parts etc
};
/**
* This class controls the workflow of each file.
* It has been introdused to support legacy files migrating to the new workflow.
*/
class PartDesignGuiExport WorkflowManager {
public:
virtual ~WorkflowManager ();
/**
* Lookup the workflow of the document in the map.
* If the document not in the map yet return Workflow::Undetermined.
*/
Workflow getWorkflowForDocument(App::Document *doc);
/**
* Asserts the workflow of the document to be determined and prompt user to migrate if it is not modern.
*
* If workflow was already choosen return it.
* If the guesed workflow is Workflow::Legacy or Workflow::Mixed the user will be prompted to migrate.
* If the user agrees the file will be migrated and the workflow will be set as modern.
* If the user refuses to migrate use the old workflow.
*/
Workflow determinWorkflow(App::Document *doc);
/**
* Force the desired workflow in document
* Note: currently added for testing purpose; May be removed later
*/
void forceWorkflow (const App::Document *doc, Workflow wf);
/** @name Init, Destruct an Access methods */
//@{
/// Creates an instance of the manager, should be called before any instance()
static void init ();
/// Return an instance of the WorkflofManager.
static WorkflowManager* instance();
/// destroy the manager
static void destruct ();
//@}
private:
/// The class is not intended to be constructed outside of itself
WorkflowManager ();
/// Get the signal on New document created
void slotNewDocument (const App::Document& doc);
/// Get the signal on document getting loaded
void slotFinishRestoreDocument (const App::Document& doc);
/// Get the signal on document close and remove it from our list
void slotDeleteDocument (const App::Document& doc);
/// Guess the Workflow of the document out of it's content
Workflow guessWorkflow(const App::Document *doc);
private:
std::map<const App::Document*, Workflow> dwMap;
boost::signals::connection connectNewDocument;
boost::signals::connection connectFinishRestoreDocument;
boost::signals::connection connectDeleteDocument;
static WorkflowManager* _instance;
};
/// Assures that workflow of the given document is determined and returns true if it is Workflow::Legacy
inline bool assureLegacyWorkflow (App::Document *doc) {
return WorkflowManager::instance()->determinWorkflow( doc ) == Workflow::Legacy ;
}
/// Assures that workflow of the given document is determined and returns true if it is Workflow::Modern
inline bool assureModernWorkflow (App::Document *doc) {
return WorkflowManager::instance()->determinWorkflow( doc ) == Workflow::Modern ;
}
/// Returns true if the workflow of the given document is Workflow::Legacy
inline bool isLegacyWorkflow (App::Document *doc) {
return WorkflowManager::instance()->getWorkflowForDocument( doc ) == Workflow::Legacy ;
}
/// Returns true if the workflow of the given document is Workflow::Modern
inline bool isModernWorkflow (App::Document *doc) {
return WorkflowManager::instance()->getWorkflowForDocument( doc ) == Workflow::Modern ;
}
} /* PartDesignGui */
#endif /* end of include guard: WORKFLOWMANAGER_H_PB7A5GCM */