Assembly: Introduce core functionality of assembly workbench.
This commit is contained in:
398
src/Mod/Assembly/Gui/ViewProviderAssembly.cpp
Normal file
398
src/Mod/Assembly/Gui/ViewProviderAssembly.cpp
Normal file
@@ -0,0 +1,398 @@
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
/****************************************************************************
|
||||
* *
|
||||
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
|
||||
* *
|
||||
* This file is part of FreeCAD. *
|
||||
* *
|
||||
* FreeCAD is free software: you can redistribute it and/or modify it *
|
||||
* under the terms of the GNU Lesser General Public License as *
|
||||
* published by the Free Software Foundation, either version 2.1 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* FreeCAD is distributed in the hope that it will be useful, but *
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
|
||||
* Lesser General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Lesser General Public *
|
||||
* License along with FreeCAD. If not, see *
|
||||
* <https://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***************************************************************************/
|
||||
|
||||
#include "PreCompiled.h"
|
||||
|
||||
#ifndef _PreComp_
|
||||
#include <vector>
|
||||
#include <sstream>
|
||||
#include <iostream>
|
||||
#endif
|
||||
|
||||
#include <App/Link.h>
|
||||
#include <App/Document.h>
|
||||
#include <App/DocumentObject.h>
|
||||
#include <App/Part.h>
|
||||
#include <Gui/Application.h>
|
||||
#include <Gui/BitmapFactory.h>
|
||||
#include <Gui/Command.h>
|
||||
#include <Gui/MDIView.h>
|
||||
#include <Gui/View3DInventor.h>
|
||||
#include <Gui/View3DInventorViewer.h>
|
||||
#include <Mod/Assembly/App/AssemblyObject.h>
|
||||
#include <Mod/PartDesign/App/Body.h>
|
||||
|
||||
#include "ViewProviderAssembly.h"
|
||||
#include "ViewProviderAssemblyPy.h"
|
||||
|
||||
|
||||
using namespace Assembly;
|
||||
using namespace AssemblyGui;
|
||||
|
||||
PROPERTY_SOURCE(AssemblyGui::ViewProviderAssembly, Gui::ViewProviderPart)
|
||||
|
||||
ViewProviderAssembly::ViewProviderAssembly()
|
||||
: SelectionObserver(true)
|
||||
, canStartDragging(false)
|
||||
, partMoving(false)
|
||||
, enableMovement(true)
|
||||
, docsToMove({})
|
||||
{}
|
||||
|
||||
ViewProviderAssembly::~ViewProviderAssembly() = default;
|
||||
|
||||
QIcon ViewProviderAssembly::getIcon() const
|
||||
{
|
||||
return Gui::BitmapFactory().pixmap("Geoassembly.svg");
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::doubleClicked()
|
||||
{
|
||||
if (isInEditMode()) {
|
||||
// Part is already 'Active' so we exit edit mode.
|
||||
Gui::Command::doCommand(Gui::Command::Gui, "Gui.activeDocument().resetEdit()");
|
||||
}
|
||||
else {
|
||||
// Part is not 'Active' so we enter edit mode to make it so.
|
||||
Gui::Application::Instance->activeDocument()->setEdit(this);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
bool ViewProviderAssembly::setEdit(int ModNum)
|
||||
{
|
||||
// Set the part as 'Activated' ie bold in the tree.
|
||||
Gui::Command::doCommand(Gui::Command::Gui,
|
||||
"Gui.ActiveDocument.ActiveView.setActiveObject('%s', "
|
||||
"App.getDocument('%s').getObject('%s'))",
|
||||
PARTKEY,
|
||||
this->getObject()->getDocument()->getName(),
|
||||
this->getObject()->getNameInDocument());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::unsetEdit(int ModNum)
|
||||
{
|
||||
Q_UNUSED(ModNum);
|
||||
|
||||
canStartDragging = false;
|
||||
partMoving = false;
|
||||
docsToMove = {};
|
||||
|
||||
// Set the part as not 'Activated' ie not bold in the tree.
|
||||
Gui::Command::doCommand(Gui::Command::Gui,
|
||||
"Gui.ActiveDocument.ActiveView.setActiveObject('%s', None)",
|
||||
PARTKEY);
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::isInEditMode()
|
||||
{
|
||||
App::DocumentObject* activePart = getActivePart();
|
||||
if (!activePart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return activePart == this->getObject();
|
||||
}
|
||||
|
||||
App::DocumentObject* ViewProviderAssembly::getActivePart()
|
||||
{
|
||||
App::DocumentObject* activePart = nullptr;
|
||||
auto activeDoc = Gui::Application::Instance->activeDocument();
|
||||
if (!activeDoc) {
|
||||
activeDoc = getDocument();
|
||||
}
|
||||
auto activeView = activeDoc->setActiveView(this);
|
||||
if (!activeView) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
activePart = activeView->getActiveObject<App::DocumentObject*>(PARTKEY);
|
||||
return activePart;
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer)
|
||||
{
|
||||
// Base::Console().Warning("Mouse move\n");
|
||||
|
||||
// Initialize or end the dragging of parts
|
||||
if (canStartDragging) {
|
||||
canStartDragging = false;
|
||||
|
||||
if (enableMovement && getSelectedObjectsWithinAssembly()) {
|
||||
SbVec3f vec = viewer->getPointOnFocalPlane(cursorPos);
|
||||
Base::Vector3d mousePosition = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
|
||||
initMove(mousePosition);
|
||||
}
|
||||
}
|
||||
|
||||
// Do the dragging of parts
|
||||
if (partMoving) {
|
||||
SbVec3f vec = viewer->getPointOnFocalPlane(cursorPos);
|
||||
Base::Vector3d mousePosition = Base::Vector3d(vec[0], vec[1], vec[2]);
|
||||
for (auto& pair : docsToMove) {
|
||||
App::DocumentObject* obj = pair.first;
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
Base::Placement plc = propPlacement->getValue();
|
||||
// Base::Console().Warning("transl %f %f %f\n", pair.second.x, pair.second.y,
|
||||
// pair.second.z);
|
||||
Base::Vector3d pos = mousePosition + pair.second;
|
||||
Base::Placement newPlacement = Base::Placement(pos, plc.getRotation());
|
||||
propPlacement->setValue(newPlacement);
|
||||
}
|
||||
}
|
||||
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
assemblyPart->solve();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::mouseButtonPressed(int Button,
|
||||
bool pressed,
|
||||
const SbVec2s& cursorPos,
|
||||
const Gui::View3DInventorViewer* viewer)
|
||||
{
|
||||
// Left Mouse button ****************************************************
|
||||
if (Button == 1) {
|
||||
if (pressed) {
|
||||
canStartDragging = true;
|
||||
}
|
||||
else { // Button 1 released
|
||||
// release event is not received when user click on a part for selection.
|
||||
// So we use SelectionObserver to know if something got selected.
|
||||
|
||||
canStartDragging = false;
|
||||
if (partMoving) {
|
||||
endMove();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ViewProviderAssembly::getSelectedObjectsWithinAssembly()
|
||||
{
|
||||
// check the current selection, and check if any of the selected objects are within this
|
||||
// App::Part
|
||||
// If any, put them into the vector docsToMove and return true.
|
||||
// Get the document
|
||||
Gui::Document* doc = Gui::Application::Instance->activeDocument();
|
||||
|
||||
if (!doc) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the assembly object for this ViewProvider
|
||||
AssemblyObject* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
|
||||
if (!assemblyPart) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (auto& selObj : Gui::Selection().getSelectionEx("",
|
||||
App::DocumentObject::getClassTypeId(),
|
||||
Gui::ResolveMode::NoResolve)) {
|
||||
// getSubNames() returns ["Body001.Pad.Face14", "Body002.Pad.Face7"]
|
||||
// if you have several objects within the same assembly selected.
|
||||
|
||||
std::vector<std::string> objsSubNames = selObj.getSubNames();
|
||||
for (auto& subNamesStr : objsSubNames) {
|
||||
std::vector<std::string> subNames = parseSubNames(subNamesStr);
|
||||
|
||||
App::DocumentObject* obj = getObjectFromSubNames(subNames);
|
||||
if (!obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the selected object is a child of the assembly
|
||||
if (assemblyPart->hasObject(obj, true)) {
|
||||
auto* propPlacement =
|
||||
dynamic_cast<App::PropertyPlacement*>(obj->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
Base::Placement plc = propPlacement->getValue();
|
||||
Base::Vector3d pos = plc.getPosition();
|
||||
docsToMove.emplace_back(obj, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called before the selection is updated. So if a user click and drag a part
|
||||
// it is not selected at that point. So we need to get the preselection too.
|
||||
if (Gui::Selection().hasPreselection()) {
|
||||
|
||||
// Base::Console().Warning("Gui::Selection().getPreselection().pSubName %s\n",
|
||||
// Gui::Selection().getPreselection().pSubName);
|
||||
|
||||
std::string subNamesStr = Gui::Selection().getPreselection().pSubName;
|
||||
std::vector<std::string> subNames = parseSubNames(subNamesStr);
|
||||
|
||||
App::DocumentObject* preselectedObj = getObjectFromSubNames(subNames);
|
||||
if (preselectedObj) {
|
||||
if (assemblyPart->hasObject(preselectedObj, true)) {
|
||||
bool alreadyIn = false;
|
||||
for (auto& pair : docsToMove) {
|
||||
App::DocumentObject* obj = pair.first;
|
||||
if (obj == preselectedObj) {
|
||||
alreadyIn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyIn) {
|
||||
auto* propPlacement = dynamic_cast<App::PropertyPlacement*>(
|
||||
preselectedObj->getPropertyByName("Placement"));
|
||||
if (propPlacement) {
|
||||
Base::Placement plc = propPlacement->getValue();
|
||||
Base::Vector3d pos = plc.getPosition();
|
||||
docsToMove.emplace_back(preselectedObj, pos);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !docsToMove.empty();
|
||||
}
|
||||
|
||||
std::vector<std::string> ViewProviderAssembly::parseSubNames(std::string& subNamesStr)
|
||||
{
|
||||
std::vector<std::string> subNames;
|
||||
std::string subName;
|
||||
std::istringstream subNameStream(subNamesStr);
|
||||
while (std::getline(subNameStream, subName, '.')) {
|
||||
subNames.push_back(subName);
|
||||
}
|
||||
return subNames;
|
||||
}
|
||||
|
||||
App::DocumentObject* ViewProviderAssembly::getObjectFromSubNames(std::vector<std::string>& subNames)
|
||||
{
|
||||
App::Document* appDoc = App::GetApplication().getActiveDocument();
|
||||
|
||||
std::string objName;
|
||||
if (subNames.size() < 2) {
|
||||
return nullptr;
|
||||
}
|
||||
else if (subNames.size() == 2) {
|
||||
// If two subnames then it can't be a body and the object we want is the first one
|
||||
// For example we want box in "box.face1"
|
||||
return appDoc->getObject(subNames[0].c_str());
|
||||
}
|
||||
else {
|
||||
objName = subNames[subNames.size() - 3];
|
||||
|
||||
App::DocumentObject* obj = appDoc->getObject(objName.c_str());
|
||||
if (!obj) {
|
||||
return nullptr;
|
||||
}
|
||||
if (obj->getTypeId().isDerivedFrom(PartDesign::Body::getClassTypeId())) {
|
||||
return obj;
|
||||
}
|
||||
else if (obj->getTypeId().isDerivedFrom(App::Link::getClassTypeId())) {
|
||||
|
||||
App::Link* link = dynamic_cast<App::Link*>(obj);
|
||||
|
||||
App::DocumentObject* linkedObj = link->getLinkedObject(true);
|
||||
|
||||
if (linkedObj->getTypeId().isDerivedFrom(PartDesign::Body::getClassTypeId())) {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
// then its neither a body or a link to a body.
|
||||
objName = subNames[subNames.size() - 2];
|
||||
return appDoc->getObject(objName.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::initMove(Base::Vector3d& mousePosition)
|
||||
{
|
||||
partMoving = true;
|
||||
|
||||
// prevent selection while moving
|
||||
auto* view = dynamic_cast<Gui::View3DInventor*>(
|
||||
Gui::Application::Instance->editDocument()->getActiveView());
|
||||
if (view) {
|
||||
Gui::View3DInventorViewer* viewerNotConst;
|
||||
viewerNotConst = static_cast<Gui::View3DInventor*>(view)->getViewer();
|
||||
viewerNotConst->setSelectionEnabled(false);
|
||||
}
|
||||
|
||||
objectMasses.clear();
|
||||
|
||||
for (auto& pair : docsToMove) {
|
||||
pair.second = pair.second - mousePosition;
|
||||
objectMasses.push_back({pair.first, 10.0});
|
||||
}
|
||||
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
assemblyPart->setObjMasses(objectMasses);
|
||||
}
|
||||
|
||||
void ViewProviderAssembly::endMove()
|
||||
{
|
||||
docsToMove = {};
|
||||
partMoving = false;
|
||||
canStartDragging = false;
|
||||
|
||||
// enable selection after the move
|
||||
auto* view = dynamic_cast<Gui::View3DInventor*>(
|
||||
Gui::Application::Instance->editDocument()->getActiveView());
|
||||
if (view) {
|
||||
Gui::View3DInventorViewer* viewerNotConst;
|
||||
viewerNotConst = static_cast<Gui::View3DInventor*>(view)->getViewer();
|
||||
viewerNotConst->setSelectionEnabled(true);
|
||||
}
|
||||
|
||||
auto* assemblyPart = static_cast<AssemblyObject*>(getObject());
|
||||
assemblyPart->setObjMasses({});
|
||||
}
|
||||
|
||||
|
||||
void ViewProviderAssembly::onSelectionChanged(const Gui::SelectionChanges& msg)
|
||||
{
|
||||
if (msg.Type == Gui::SelectionChanges::AddSelection
|
||||
|| msg.Type == Gui::SelectionChanges::ClrSelection
|
||||
|| msg.Type == Gui::SelectionChanges::RmvSelection) {
|
||||
canStartDragging = false;
|
||||
}
|
||||
}
|
||||
|
||||
PyObject* ViewProviderAssembly::getPyObject()
|
||||
{
|
||||
if (!pyViewObject) {
|
||||
pyViewObject = new ViewProviderAssemblyPy(this);
|
||||
}
|
||||
pyViewObject->IncRef();
|
||||
return pyViewObject;
|
||||
}
|
||||
Reference in New Issue
Block a user