diff --git a/src/Mod/Assembly/App/AppAssembly.cpp b/src/Mod/Assembly/App/AppAssembly.cpp index e9152d4dae..a2679171a7 100644 --- a/src/Mod/Assembly/App/AppAssembly.cpp +++ b/src/Mod/Assembly/App/AppAssembly.cpp @@ -28,6 +28,7 @@ #include #include "AssemblyObject.h" +#include "AssemblyLink.h" #include "BomObject.h" #include "BomGroup.h" #include "JointGroup.h" @@ -61,6 +62,7 @@ PyMOD_INIT_FUNC(AssemblyApp) // This function is responsible for adding inherited slots from a type's base class. Assembly::AssemblyObject ::init(); + Assembly::AssemblyLink ::init(); Assembly::BomObject ::init(); Assembly::BomGroup ::init(); diff --git a/src/Mod/Assembly/App/AssemblyLink.cpp b/src/Mod/Assembly/App/AssemblyLink.cpp new file mode 100644 index 0000000000..f8df4f771e --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLink.cpp @@ -0,0 +1,582 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Ondsel * + * * + * 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 * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" +#ifndef _PreComp_ +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "AssemblyObject.h" +#include "JointGroup.h" + +#include "AssemblyLink.h" +#include "AssemblyLinkPy.h" + +namespace PartApp = Part; + +using namespace Assembly; + +// ================================ Assembly Object ============================ + +PROPERTY_SOURCE(Assembly::AssemblyLink, App::Part) + +AssemblyLink::AssemblyLink() +{ + ADD_PROPERTY_TYPE(Rigid, + (true), + "General", + (App::PropertyType)(App::Prop_None), + "If the sub-assembly is set to Rigid, it will act " + "as a rigid body. Else its joints will be taken into account."); + + ADD_PROPERTY_TYPE(LinkedObject, + (nullptr), + "General", + (App::PropertyType)(App::Prop_None), + "The linked assembly."); +} + +AssemblyLink::~AssemblyLink() = default; + +PyObject* AssemblyLink::getPyObject() +{ + if (PythonObject.is(Py::_None())) { + // ref counter is set to 1 + PythonObject = Py::Object(new AssemblyLinkPy(this), true); + } + return Py::new_reference_to(PythonObject); +} + +App::DocumentObjectExecReturn* AssemblyLink::execute() +{ + updateContents(); + + return App::Part::execute(); +} + +void AssemblyLink::onChanged(const App::Property* prop) +{ + if (App::GetApplication().isRestoring()) { + App::Part::onChanged(prop); + return; + } + + if (prop == &Rigid) { + Base::Placement movePlc; + + if (Rigid.getValue()) { + // movePlc needs to be computed before updateContents. + if (!objLinkMap.empty()) { + auto firstElement = *objLinkMap.begin(); + + App::DocumentObject* obj = firstElement.first; + App::DocumentObject* link = firstElement.second; + auto* prop = + dynamic_cast(obj->getPropertyByName("Placement")); + auto* prop2 = + dynamic_cast(link->getPropertyByName("Placement")); + if (prop && prop2) { + movePlc = prop2->getValue() * prop->getValue().inverse(); + } + } + } + + updateContents(); + + auto* propPlc = dynamic_cast(getPropertyByName("Placement")); + if (!propPlc) { + return; + } + + if (!Rigid.getValue()) { + // when the assemblyLink becomes flexible, we need to make sure its placement is + // identity or it's going to mess up moving parts placement within. + Base::Placement plc = propPlc->getValue(); + if (!plc.isIdentity()) { + propPlc->setValue(Base::Placement()); + + // We need to apply the placement of the assembly link to the children or they will + // move. + std::vector group = Group.getValues(); + for (auto* obj : group) { + if (!obj->isDerivedFrom() && !obj->isDerivedFrom() + && !obj->isDerivedFrom()) { + continue; + } + + auto* prop = + dynamic_cast(obj->getPropertyByName("Placement")); + if (prop) { + prop->setValue(plc * prop->getValue()); + } + } + + AssemblyObject::redrawJointPlacements(getJoints()); + } + } + else { + // For the assemblylink not to move to origin, we need to update its placement. + if (!movePlc.isIdentity()) { + propPlc->setValue(movePlc); + } + } + + return; + } + App::Part::onChanged(prop); +} + +void AssemblyLink::updateContents() +{ + synchronizeComponents(); + + if (isRigid()) { + ensureNoJointGroup(); + } + else { + synchronizeJoints(); + } + + purgeTouched(); +} + +void AssemblyLink::synchronizeComponents() +{ + App::Document* doc = getDocument(); + + AssemblyObject* assembly = getLinkedAssembly(); + if (!assembly) { + return; + } + + objLinkMap.clear(); + + std::vector assemblyGroup = assembly->Group.getValues(); + std::vector assemblyLinkGroup = Group.getValues(); + + // We check if a component needs to be added to the AssemblyLink + for (auto* obj : assemblyGroup) { + if (!obj->isDerivedFrom() && !obj->isDerivedFrom() + && !obj->isDerivedFrom()) { + continue; + } + + // Note, the user can have nested sub-assemblies. + // In which case we need to add an AssemblyLink and not a Link. + App::DocumentObject* link = nullptr; + bool found = false; + for (auto* obj2 : assemblyLinkGroup) { + App::DocumentObject* linkedObj; + + auto* subAsmLink = dynamic_cast(obj2); + auto* link2 = dynamic_cast(obj2); + if (subAsmLink) { + linkedObj = subAsmLink->getLinkedObject2(false); // not recursive + } + else if (link2) { + linkedObj = link2->getLinkedObject(false); // not recursive + } + else { + // We consider only Links and AssemblyLinks in the AssemblyLink. + continue; + } + + + if (linkedObj == obj) { + found = true; + link = obj2; + break; + } + } + if (!found) { + // Add a link or a AssemblyLink to it in the AssemblyLink. + if (obj->isDerivedFrom()) { + auto* asmLink = static_cast(obj); + auto* subAsmLink = new AssemblyLink(); + doc->addObject(subAsmLink, obj->getNameInDocument()); + subAsmLink->LinkedObject.setValue(obj); + subAsmLink->Rigid.setValue(asmLink->Rigid.getValue()); + subAsmLink->Label.setValue(obj->Label.getValue()); + addObject(subAsmLink); + link = subAsmLink; + } + else { + auto* appLink = new App::Link(); + doc->addObject(appLink, obj->getNameInDocument()); + appLink->LinkedObject.setValue(obj); + appLink->Label.setValue(obj->Label.getValue()); + addObject(appLink); + link = appLink; + } + } + + objLinkMap[obj] = link; + // If the assemblyLink is rigid, then we keep the placement synchronized. + if (isRigid()) { + auto* plcProp = + dynamic_cast(obj->getPropertyByName("Placement")); + auto* plcProp2 = + dynamic_cast(link->getPropertyByName("Placement")); + if (plcProp && plcProp2) { + if (!plcProp->getValue().isSame(plcProp2->getValue())) { + plcProp2->setValue(plcProp->getValue()); + } + } + } + } + + // We check if a component needs to be removed from the AssemblyLink + for (auto* link : assemblyLinkGroup) { + // We don't need to update assemblyLinkGroup after the addition since we're not removing + // something we just added. + + if (objLinkMap.find(link) != objLinkMap.end()) { + doc->removeObject(link->getNameInDocument()); + } + + /*if (!link->isDerivedFrom() && !link->isDerivedFrom()) { + // AssemblyLink should contain only Links or assembly links. + continue; + } + + auto* linkedObj = link->getLinkedObject(false); // not recursive + + bool found = false; + for (auto* obj2 : assemblyGroup) { + if (obj2 == linkedObj) { + found = true; + break; + } + } + if (!found) { + doc->removeObject(link->getNameInDocument()); + }*/ + } +} + +namespace +{ +template +void copyPropertyIfDifferent(App::DocumentObject* source, + App::DocumentObject* target, + const char* propertyName) +{ + auto sourceProp = dynamic_cast(source->getPropertyByName(propertyName)); + auto targetProp = dynamic_cast(target->getPropertyByName(propertyName)); + if (sourceProp && targetProp && sourceProp->getValue() != targetProp->getValue()) { + targetProp->setValue(sourceProp->getValue()); + } +} + +std::string removeUpToName(const std::string& sub, const std::string& name) +{ + size_t pos = sub.find(name); + if (pos != std::string::npos) { + // Move the position to the character after the found substring and the following '.' + pos += name.length() + 1; + if (pos < sub.length()) { + return sub.substr(pos); + } + } + // If s2 is not found in s1, return the original string + return sub; +} + +std::string +replaceLastOccurrence(const std::string& str, const std::string& oldStr, const std::string& newStr) +{ + size_t pos = str.rfind(oldStr); + if (pos != std::string::npos) { + std::string result = str; + result.replace(pos, oldStr.length(), newStr); + return result; + } + return str; +} +}; // namespace + +void AssemblyLink::synchronizeJoints() +{ + App::Document* doc = getDocument(); + AssemblyObject* assembly = getLinkedAssembly(); + if (!assembly) { + return; + } + + JointGroup* jGroup = ensureJointGroup(); + + std::vector assemblyJoints = + assembly->getJoints(assembly->isTouched(), false, false); + std::vector assemblyLinkJoints = getJoints(); + + // We delete the excess of joints if any + for (size_t i = assemblyJoints.size(); i < assemblyLinkJoints.size(); ++i) { + doc->removeObject(assemblyLinkJoints[i]->getNameInDocument()); + } + + // We make sure the joints match. + for (size_t i = 0; i < assemblyJoints.size(); ++i) { + App::DocumentObject* joint = assemblyJoints[i]; + App::DocumentObject* lJoint; + if (i < assemblyLinkJoints.size()) { + lJoint = assemblyLinkJoints[i]; + } + else { + auto ret = doc->copyObject({joint}); + if (ret.size() != 1) { + continue; + } + lJoint = ret[0]; + jGroup->addObject(lJoint); + } + + // Then we have to check the properties one by one. + copyPropertyIfDifferent(joint, lJoint, "Activated"); + copyPropertyIfDifferent(joint, lJoint, "Distance"); + copyPropertyIfDifferent(joint, lJoint, "Distance2"); + copyPropertyIfDifferent(joint, lJoint, "JointType"); + copyPropertyIfDifferent(joint, lJoint, "Offset1"); + copyPropertyIfDifferent(joint, lJoint, "Offset2"); + + copyPropertyIfDifferent(joint, lJoint, "Detach1"); + copyPropertyIfDifferent(joint, lJoint, "Detach2"); + + copyPropertyIfDifferent(joint, lJoint, "AngleMax"); + copyPropertyIfDifferent(joint, lJoint, "AngleMin"); + copyPropertyIfDifferent(joint, lJoint, "LengthMax"); + copyPropertyIfDifferent(joint, lJoint, "LengthMin"); + copyPropertyIfDifferent(joint, lJoint, "EnableAngleMax"); + copyPropertyIfDifferent(joint, lJoint, "EnableAngleMin"); + copyPropertyIfDifferent(joint, lJoint, "EnableLengthMax"); + copyPropertyIfDifferent(joint, lJoint, "EnableLengthMin"); + + // The reference needs to be handled specifically + handleJointReference(joint, lJoint, "Reference1"); + handleJointReference(joint, lJoint, "Reference2"); + } + + assemblyLinkJoints = getJoints(); + + AssemblyObject::recomputeJointPlacements(assemblyLinkJoints); + + for (auto* joint : assemblyLinkJoints) { + joint->purgeTouched(); + } +} + + +void AssemblyLink::handleJointReference(App::DocumentObject* joint, + App::DocumentObject* lJoint, + const char* refName) +{ + AssemblyObject* assembly = getLinkedAssembly(); + + auto prop1 = dynamic_cast(joint->getPropertyByName(refName)); + auto prop2 = dynamic_cast(lJoint->getPropertyByName(refName)); + if (!prop1 || !prop2) { + return; + } + + App::DocumentObject* obj1 = nullptr; + App::DocumentObject* obj2 = prop2->getValue(); + std::vector subs1 = prop1->getSubValues(); + std::vector subs2 = prop2->getSubValues(); + if (subs1.empty()) { + return; + } + + // Example : + // Obj1 = docA-Asm1 Subs1 = ["part1.body.pad.face0", "part1.body.pad.vertex1"] + // Obj1 = docA-Part Subs1 = ["Asm1.part1.body.pad.face0", "Asm1.part1.body.pad.vertex1"] // some + // user may put the assembly inside a part... should become : Obj2 = docB-Asm2 Subs2 = + // ["Asm1Link.part1.linkTobody.pad.face0", "Asm1Link.part1.linkTobody.pad.vertex1"] Obj2 = + // docB-Part Sub2 = ["Asm2.Asm1Link.part1.linkTobody.pad.face0", + // "Asm2.Asm1Link.part1.linkTobody.pad.vertex1"] + + std::string asmLink = getNameInDocument(); + for (auto& sub : subs1) { + // First let's remove 'Asm1' name and everything before if any. + sub = removeUpToName(sub, assembly->getNameInDocument()); + // Then we add the assembly link name. + sub = asmLink + "." + sub; + // Then the question is, is there more to prepend? Because the parent assembly may have some + // parents So we check assemblyLink parents and prepend necessary parents. + bool first = true; + std::vector inList = getInList(); + int limit = 0; + while (!inList.empty() && limit < 20) { + ++limit; + bool found = false; + for (auto* obj : inList) { + if (obj->isDerivedFrom()) { + found = true; + if (first) { + first = false; + } + else { + std::string obj1Name = obj1->getNameInDocument(); + sub = obj1Name + "." + sub; + } + obj1 = obj; + break; + } + } + if (found) { + inList = obj1->getInList(); + } + else { + inList = {}; + } + } + + // Lastly we need to replace the object name by its link name. + auto* obj = AssemblyObject::getObjFromRef(prop1); + auto* link = objLinkMap[obj]; + if (!obj || !link) { + return; + } + std::string objName = obj->getNameInDocument(); + std::string linkName = link->getNameInDocument(); + sub = replaceLastOccurrence(sub, objName, linkName); + } + // Now obj1 and the subs1 are what should be in obj2 and subs2 if the joint did not changed + if (obj1 != obj2) { + prop2->setValue(obj1); + } + bool changed = false; + for (size_t i = 0; i < subs1.size(); ++i) { + if (i >= subs2.size() || subs1[i] != subs2[i]) { + changed = true; + break; + } + } + if (changed) { + prop2->setSubValues(std::move(subs1)); + } +} + +void AssemblyLink::ensureNoJointGroup() +{ + // Make sure there is no joint group + JointGroup* jGroup = AssemblyObject::getJointGroup(this); + if (jGroup) { + // If there is a joint group, we delete it and its content. + jGroup->removeObjectsFromDocument(); + getDocument()->removeObject(jGroup->getNameInDocument()); + } +} +JointGroup* AssemblyLink::ensureJointGroup() +{ + // Make sure there is a jointGroup + JointGroup* jGroup = AssemblyObject::getJointGroup(this); + if (!jGroup) { + jGroup = new JointGroup(); + getDocument()->addObject(jGroup, tr("Joints").toStdString().c_str()); + + // we want to add jgroup at the start, so we don't use + // addObject(jGroup); + std::vector grp = Group.getValues(); + grp.insert(grp.begin(), jGroup); + Group.setValues(grp); + } + return jGroup; +} + +App::DocumentObject* AssemblyLink::getLinkedObject2(bool recursive) const +{ + auto* obj = LinkedObject.getValue(); + auto* assembly = dynamic_cast(obj); + if (assembly) { + return assembly; + } + else { + auto* assemblyLink = dynamic_cast(obj); + if (assemblyLink) { + if (recursive) { + return assemblyLink->getLinkedObject2(recursive); + } + else { + return assemblyLink; + } + } + } + + return nullptr; +} + +AssemblyObject* AssemblyLink::getLinkedAssembly() const +{ + return dynamic_cast(getLinkedObject2()); +} + +AssemblyObject* AssemblyLink::getParentAssembly() const +{ + std::vector inList = getInList(); + for (auto* obj : inList) { + auto* assembly = dynamic_cast(obj); + if (assembly) { + return assembly; + } + } + + return nullptr; +} + +bool AssemblyLink::isRigid() +{ + auto* prop = dynamic_cast(getPropertyByName("Rigid")); + if (!prop) { + return true; + } + + return prop->getValue(); +} + +std::vector AssemblyLink::getJoints() +{ + JointGroup* jointGroup = AssemblyObject::getJointGroup(this); + + if (!jointGroup) { + return {}; + } + + return jointGroup->getJoints(); +} diff --git a/src/Mod/Assembly/App/AssemblyLink.h b/src/Mod/Assembly/App/AssemblyLink.h new file mode 100644 index 0000000000..5bbcfefa40 --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLink.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Ondsel * + * * + * 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 * + * . * + * * + ***************************************************************************/ + + +#ifndef ASSEMBLY_AssemblyLink_H +#define ASSEMBLY_AssemblyLink_H + +#include + +#include + +#include +#include +#include + + +namespace Assembly +{ +class AssemblyObject; +class JointGroup; + +class AssemblyExport AssemblyLink: public App::Part +{ + PROPERTY_HEADER_WITH_OVERRIDE(Assembly::AssemblyLink); + +public: + AssemblyLink(); + ~AssemblyLink() override; + + PyObject* getPyObject() override; + + /// returns the type name of the ViewProvider + const char* getViewProviderName() const override + { + return "AssemblyGui::ViewProviderAssemblyLink"; + } + + App::DocumentObjectExecReturn* execute() override; + + // The linked assembly is the AssemblyObject that this AssemblyLink pseudo-links to recursively. + AssemblyObject* getLinkedAssembly() const; + // The parent assembly is the main assembly in which the linked assembly is contained + AssemblyObject* getParentAssembly() const; + + // Overriding DocumentObject::getLinkedObject is giving bugs + // This function returns the linked object, either an AssemblyObject or an AssemblyLink + App::DocumentObject* getLinkedObject2(bool recurse = true) const; + + bool isRigid(); + + void updateContents(); + + void synchronizeComponents(); + void synchronizeJoints(); + void handleJointReference(App::DocumentObject* joint, + App::DocumentObject* lJoint, + const char* refName); + void ensureNoJointGroup(); + JointGroup* ensureJointGroup(); + std::vector getJoints(); + + App::PropertyXLink LinkedObject; + App::PropertyBool Rigid; + + std::unordered_map objLinkMap; + +protected: + /// get called by the container whenever a property has been changed + void onChanged(const App::Property* prop) override; +}; + + +} // namespace Assembly + + +#endif // ASSEMBLY_AssemblyLink_H diff --git a/src/Mod/Assembly/App/AssemblyLinkPy.xml b/src/Mod/Assembly/App/AssemblyLinkPy.xml new file mode 100644 index 0000000000..2b72b7af31 --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLinkPy.xml @@ -0,0 +1,19 @@ + + + + + + This class handles document objects in Assembly + + + + + diff --git a/src/Mod/Assembly/App/AssemblyLinkPyImp.cpp b/src/Mod/Assembly/App/AssemblyLinkPyImp.cpp new file mode 100644 index 0000000000..2a7123a213 --- /dev/null +++ b/src/Mod/Assembly/App/AssemblyLinkPyImp.cpp @@ -0,0 +1,47 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Ondsel * + * * + * 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 * + * . * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +// inclusion of the generated files (generated out of AssemblyLink.xml) +#include "AssemblyLinkPy.h" +#include "AssemblyLinkPy.cpp" + +using namespace Assembly; + +// returns a string which represents the object e.g. when printed in python +std::string AssemblyLinkPy::representation() const +{ + return {""}; +} + +PyObject* AssemblyLinkPy::getCustomAttributes(const char* /*attr*/) const +{ + return nullptr; +} + +int AssemblyLinkPy::setCustomAttributes(const char* /*attr*/, PyObject* /*obj*/) +{ + return 0; +} diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index aba23b402b..cf74c3f830 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -82,6 +82,7 @@ #include #include +#include "AssemblyLink.h" #include "AssemblyObject.h" #include "AssemblyObjectPy.h" #include "JointGroup.h" @@ -445,6 +446,7 @@ void AssemblyObject::redrawJointPlacement(App::DocumentObject* joint) void AssemblyObject::recomputeJointPlacements(std::vector joints) { // The Placement1 and Placement2 of each joint needs to be updated as the parts moved. + Base::PyGILStateLocker lock; for (auto* joint : joints) { if (!joint) { continue; @@ -1773,17 +1775,17 @@ void AssemblyObject::setObjMasses(std::vector AssemblyObject::getSubAssemblies() +std::vector AssemblyObject::getSubAssemblies() { - std::vector subAssemblies = {}; + std::vector subAssemblies = {}; App::Document* doc = getDocument(); std::vector assemblies = - doc->getObjectsOfType(Assembly::AssemblyObject::getClassTypeId()); + doc->getObjectsOfType(Assembly::AssemblyLink::getClassTypeId()); for (auto assembly : assemblies) { if (hasObject(assembly)) { - subAssemblies.push_back(dynamic_cast(assembly)); + subAssemblies.push_back(dynamic_cast(assembly)); } } @@ -2465,6 +2467,14 @@ App::DocumentObject* AssemblyObject::getMovingPartFromRef(App::DocumentObject* o continue; } + // We ignore dynamic sub-assemblies. + if (obj->isDerivedFrom()) { + auto* pRigid = dynamic_cast(obj->getPropertyByName("Rigid")); + if (pRigid && !pRigid->getValue()) { + continue; + } + } + return obj; } diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index 1ead41f729..b2a083767f 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -59,6 +59,7 @@ class Rotation; namespace Assembly { +class AssemblyLink; class JointGroup; class ViewGroup; @@ -241,7 +242,7 @@ public: double getObjMass(App::DocumentObject* obj); void setObjMasses(std::vector> objectMasses); - std::vector getSubAssemblies(); + std::vector getSubAssemblies(); void updateGroundedJointsPlacements(); private: diff --git a/src/Mod/Assembly/App/CMakeLists.txt b/src/Mod/Assembly/App/CMakeLists.txt index afbf1c1870..70063733c0 100644 --- a/src/Mod/Assembly/App/CMakeLists.txt +++ b/src/Mod/Assembly/App/CMakeLists.txt @@ -19,6 +19,7 @@ set(Assembly_LIBS ) generate_from_xml(AssemblyObjectPy) +generate_from_xml(AssemblyLinkPy) generate_from_xml(BomObjectPy) generate_from_xml(BomGroupPy) generate_from_xml(JointGroupPy) @@ -27,6 +28,8 @@ generate_from_xml(ViewGroupPy) SET(Python_SRCS AssemblyObjectPy.xml AssemblyObjectPyImp.cpp + AssemblyLinkPy.xml + AssemblyLinkPyImp.cpp BomObjectPy.xml BomObjectPyImp.cpp BomGroupPy.xml @@ -49,6 +52,8 @@ SOURCE_GROUP("Module" FILES ${Module_SRCS}) SET(Assembly_SRCS AssemblyObject.cpp AssemblyObject.h + AssemblyLink.cpp + AssemblyLink.h BomObject.cpp BomObject.h BomGroup.cpp diff --git a/src/Mod/Assembly/App/JointGroup.cpp b/src/Mod/Assembly/App/JointGroup.cpp index faccfd4de7..c38a4f16d2 100644 --- a/src/Mod/Assembly/App/JointGroup.cpp +++ b/src/Mod/Assembly/App/JointGroup.cpp @@ -53,3 +53,31 @@ PyObject* JointGroup::getPyObject() } return Py::new_reference_to(PythonObject); } + + +std::vector JointGroup::getJoints() +{ + std::vector joints = {}; + + Base::PyGILStateLocker lock; + for (auto joint : getObjects()) { + if (!joint) { + continue; + } + + auto* prop = dynamic_cast(joint->getPropertyByName("Activated")); + if (!prop || !prop->getValue()) { + // Filter grounded joints and deactivated joints. + continue; + } + + auto proxy = dynamic_cast(joint->getPropertyByName("Proxy")); + if (proxy) { + if (proxy->getValue().hasAttr("setJointConnectors")) { + joints.push_back(joint); + } + } + } + + return joints; +} diff --git a/src/Mod/Assembly/App/JointGroup.h b/src/Mod/Assembly/App/JointGroup.h index 17c328d2ba..8f4611815c 100644 --- a/src/Mod/Assembly/App/JointGroup.h +++ b/src/Mod/Assembly/App/JointGroup.h @@ -49,6 +49,8 @@ public: { return "AssemblyGui::ViewProviderJointGroup"; } + + std::vector getJoints(); }; diff --git a/src/Mod/Assembly/CommandInsertLink.py b/src/Mod/Assembly/CommandInsertLink.py index a5312339b6..a9a1b74dcd 100644 --- a/src/Mod/Assembly/CommandInsertLink.py +++ b/src/Mod/Assembly/CommandInsertLink.py @@ -98,6 +98,7 @@ class TaskAssemblyInsertLink(QtCore.QObject): pref = Preferences.preferences() self.form.CheckBox_ShowOnlyParts.setChecked(pref.GetBool("InsertShowOnlyParts", False)) + self.form.CheckBox_RigidSubAsm.setChecked(pref.GetBool("InsertRigidSubAssemblies", True)) # Actions self.form.openFileButton.clicked.connect(self.openFiles) @@ -165,6 +166,7 @@ class TaskAssemblyInsertLink(QtCore.QObject): def deactivated(self): pref = Preferences.preferences() pref.SetBool("InsertShowOnlyParts", self.form.CheckBox_ShowOnlyParts.isChecked()) + pref.SetBool("InsertRigidSubAssemblies", self.form.CheckBox_RigidSubAsm.isChecked()) Gui.Selection.clearSelection() def buildPartList(self): @@ -353,7 +355,16 @@ class TaskAssemblyInsertLink(QtCore.QObject): print(selectedPart.Document.Name) documentItem.setText(0, f"{newDocName}.FCStd")""" - addedObject = self.assembly.newObject("App::Link", selectedPart.Label) + if selectedPart.isDerivedFrom("Assembly::AssemblyObject"): + objType = "Assembly::AssemblyLink" + else: + objType = "App::Link" + + addedObject = self.assembly.newObject(objType, selectedPart.Label) + + if selectedPart.isDerivedFrom("Assembly::AssemblyObject"): + addedObject.Rigid = self.form.CheckBox_RigidSubAsm.isChecked() + # set placement of the added object to the center of the screen. view = Gui.activeView() x, y = view.getSize() diff --git a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp index c76bfd1a8a..6b4ec250e9 100644 --- a/src/Mod/Assembly/Gui/AppAssemblyGui.cpp +++ b/src/Mod/Assembly/Gui/AppAssemblyGui.cpp @@ -28,6 +28,7 @@ #include #include "ViewProviderAssembly.h" +#include "ViewProviderAssemblyLink.h" #include "ViewProviderBom.h" #include "ViewProviderBomGroup.h" #include "ViewProviderJointGroup.h" @@ -60,6 +61,7 @@ PyMOD_INIT_FUNC(AssemblyGui) // This function is responsible for adding inherited slots from a type's base class. AssemblyGui::ViewProviderAssembly::init(); + AssemblyGui::ViewProviderAssemblyLink::init(); AssemblyGui::ViewProviderBom::init(); AssemblyGui::ViewProviderBomGroup::init(); AssemblyGui::ViewProviderJointGroup::init(); diff --git a/src/Mod/Assembly/Gui/CMakeLists.txt b/src/Mod/Assembly/Gui/CMakeLists.txt index 13bf42af35..7dda6251f0 100644 --- a/src/Mod/Assembly/Gui/CMakeLists.txt +++ b/src/Mod/Assembly/Gui/CMakeLists.txt @@ -40,6 +40,8 @@ SET(AssemblyGui_SRCS_Module PreCompiled.h ViewProviderAssembly.cpp ViewProviderAssembly.h + ViewProviderAssemblyLink.cpp + ViewProviderAssemblyLink.h ViewProviderBom.cpp ViewProviderBom.h ViewProviderBomGroup.cpp diff --git a/src/Mod/Assembly/Gui/Resources/Assembly.qrc b/src/Mod/Assembly/Gui/Resources/Assembly.qrc index c16225e37d..22b93155a1 100644 --- a/src/Mod/Assembly/Gui/Resources/Assembly.qrc +++ b/src/Mod/Assembly/Gui/Resources/Assembly.qrc @@ -1,5 +1,7 @@ + icons/Assembly_AssemblyLink.svg + icons/Assembly_AssemblyLinkRigid.svg icons/Assembly_InsertLink.svg icons/preferences-assembly.svg icons/Assembly_ToggleGrounded.svg diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLink.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLink.svg new file mode 100644 index 0000000000..c40a4f8a9a --- /dev/null +++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLink.svg @@ -0,0 +1,359 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Path-Stock + 2015-07-04 + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Stock.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLinkRigid.svg b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLinkRigid.svg new file mode 100644 index 0000000000..f9e6c2924f --- /dev/null +++ b/src/Mod/Assembly/Gui/Resources/icons/Assembly_AssemblyLinkRigid.svg @@ -0,0 +1,354 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + Path-Stock + 2015-07-04 + https://www.freecad.org/wiki/index.php?title=Artwork + + + FreeCAD + + + FreeCAD/src/Mod/Path/Gui/Resources/icons/Path-Stock.svg + + + FreeCAD LGPL2+ + + + https://www.gnu.org/copyleft/lesser.html + + + [agryson] Alexander Gryson + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui index 4c485ffc75..640f9a93c1 100644 --- a/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui +++ b/src/Mod/Assembly/Gui/Resources/panels/TaskAssemblyInsertLink.ui @@ -68,6 +68,28 @@ + + + + If checked, the inserted sub-assemblies will not be flexible. +Rigid means that the sub-assembly will be considered as a solid. +Flexible means that the sub-assembly joints will be taken into account in the main assembly. +You can change this property of sub-assemblies at any time by right clicking them. + + + Rigid sub-assemblies + + + true + + + InsertRigidSubAssemblies + + + Mod/Assembly + + + diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index a3e35fcfd4..22e878c4b6 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -63,6 +63,7 @@ #include #include #include +#include #include #include "ViewProviderAssembly.h" @@ -1024,7 +1025,7 @@ bool ViewProviderAssembly::onDelete(const std::vector& subNames) for (auto obj : getObject()->getOutList()) { if (obj->getTypeId() == Assembly::JointGroup::getClassTypeId() || obj->getTypeId() == Assembly::ViewGroup::getClassTypeId() - /* || obj->getTypeId() == Assembly::BomGroup::getClassTypeId()*/) { + || obj->getTypeId() == Assembly::BomGroup::getClassTypeId()) { // Delete the group content first. Gui::Command::doCommand(Gui::Command::Doc, diff --git a/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.cpp b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.cpp new file mode 100644 index 0000000000..37c1c89fb8 --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.cpp @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Ondsel * + * * + * 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 * + * . * + * * + ***************************************************************************/ + +#include "PreCompiled.h" + +#ifndef _PreComp_ +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include "ViewProviderAssembly.h" +#include "ViewProviderAssemblyLink.h" + + +using namespace Assembly; +using namespace AssemblyGui; + + +PROPERTY_SOURCE(AssemblyGui::ViewProviderAssemblyLink, Gui::ViewProviderPart) + +ViewProviderAssemblyLink::ViewProviderAssemblyLink() +{} + +ViewProviderAssemblyLink::~ViewProviderAssemblyLink() = default; + +QIcon ViewProviderAssemblyLink::getIcon() const +{ + auto* assembly = dynamic_cast(getObject()); + if (assembly->isRigid()) { + return Gui::BitmapFactory().pixmap("Assembly_AssemblyLinkRigid.svg"); + } + else { + return Gui::BitmapFactory().pixmap("Assembly_AssemblyLink.svg"); + } +} + +bool ViewProviderAssemblyLink::setEdit(int mode) +{ + auto* assemblyLink = dynamic_cast(getObject()); + + if (!assemblyLink->isRigid() && mode == (int)ViewProvider::Transform) { + Base::Console().UserTranslatedNotification( + "Flexible sub-assemblies cannot be transformed."); + return true; + } + + return ViewProviderPart::setEdit(mode); +} + +bool ViewProviderAssemblyLink::doubleClicked() +{ + auto* link = dynamic_cast(getObject()); + + if (!link) { + return true; + } + auto* assembly = link->getLinkedAssembly(); + + auto* vpa = + dynamic_cast(Gui::Application::Instance->getViewProvider(assembly)); + if (!vpa) { + return true; + } + + return vpa->doubleClicked(); +} + +bool ViewProviderAssemblyLink::onDelete(const std::vector& subNames) +{ + Q_UNUSED(subNames) + + Base::Console().Warning("onDelete\n"); + + Gui::Command::doCommand(Gui::Command::Doc, + "App.getDocument(\"%s\").getObject(\"%s\").removeObjectsFromDocument()", + getObject()->getDocument()->getName(), + getObject()->getNameInDocument()); + + // getObject()->purgeTouched(); + + return ViewProviderPart::onDelete(subNames); +} + +void ViewProviderAssemblyLink::setupContextMenu(QMenu* menu, QObject* receiver, const char* member) +{ + auto func = new Gui::ActionFunction(menu); + QAction* act; + auto* assemblyLink = dynamic_cast(getObject()); + if (assemblyLink->isRigid()) { + act = menu->addAction(QObject::tr("Turn flexible")); + act->setToolTip(QObject::tr( + "Your sub-assembly is currently rigid. This will make it flexible instead.")); + } + else { + act = menu->addAction(QObject::tr("Turn rigid")); + act->setToolTip(QObject::tr( + "Your sub-assembly is currently flexible. This will make it rigid instead.")); + } + + func->trigger(act, [this]() { + auto* assemblyLink = dynamic_cast(getObject()); + Gui::Command::openCommand(QT_TRANSLATE_NOOP("Command", "Toggle Rigid")); + Gui::cmdAppObjectArgs(assemblyLink, + "Rigid = %s", + assemblyLink->Rigid.getValue() ? "False" : "True"); + + Gui::Command::commitCommand(); + Gui::Selection().clearSelection(); + }); + + Q_UNUSED(receiver) + Q_UNUSED(member) +} diff --git a/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.h b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.h new file mode 100644 index 0000000000..190deb32b4 --- /dev/null +++ b/src/Mod/Assembly/Gui/ViewProviderAssemblyLink.h @@ -0,0 +1,81 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/**************************************************************************** + * * + * Copyright (c) 2024 Ondsel * + * * + * 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 * + * . * + * * + ***************************************************************************/ + +#ifndef ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssemblyLink_H +#define ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssemblyLink_H + +#include + +#include + +#include + + +namespace AssemblyGui +{ + +class AssemblyGuiExport ViewProviderAssemblyLink: public Gui::ViewProviderPart +{ + Q_DECLARE_TR_FUNCTIONS(AssemblyGui::ViewProviderAssemblyLink) + PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderAssemblyLink); + +public: + ViewProviderAssemblyLink(); + ~ViewProviderAssemblyLink() override; + + /// deliver the icon shown in the tree view. Override from ViewProvider.h + QIcon getIcon() const override; + + bool setEdit(int ModNum) override; + + bool doubleClicked() override; + + // When the assembly link is deleted, we delete all its content as well. + bool onDelete(const std::vector& subNames) override; + + // Prevent deletion of the link assembly's content. + bool canDelete(App::DocumentObject*) const override + { + return false; + }; + + // Prevent drag/drop of objects within the assembly link. + bool canDragObjects() const override + { + return false; + }; + bool canDropObjects() const override + { + return false; + }; + bool canDragAndDropObject(App::DocumentObject*) const override + { + return false; + }; + + void setupContextMenu(QMenu*, QObject*, const char*) override; +}; + +} // namespace AssemblyGui + +#endif // ASSEMBLYGUI_VIEWPROVIDER_ViewProviderAssemblyLink_H diff --git a/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp b/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp index 9f598654ec..f56dbde95e 100644 --- a/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderJointGroup.cpp @@ -47,10 +47,3 @@ QIcon ViewProviderJointGroup::getIcon() const { return Gui::BitmapFactory().pixmap("Assembly_JointGroup.svg"); } - -// Make the joint group impossible to delete. -bool ViewProviderJointGroup::onDelete(const std::vector& subNames) -{ - Q_UNUSED(subNames); - return false; -} diff --git a/src/Mod/Assembly/Gui/ViewProviderJointGroup.h b/src/Mod/Assembly/Gui/ViewProviderJointGroup.h index 76ca1b6230..96ccbc4850 100644 --- a/src/Mod/Assembly/Gui/ViewProviderJointGroup.h +++ b/src/Mod/Assembly/Gui/ViewProviderJointGroup.h @@ -57,7 +57,11 @@ public: return false; }; - bool onDelete(const std::vector& subNames) override; + // Make the joint group impossible to delete. + bool onDelete(const std::vector&) override + { + return false; + }; // protected: /// get called by the container whenever a property has been changed diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 9b7743a3d7..279c6d2868 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -1112,6 +1112,10 @@ def getMovingPart(assembly, ref): if obj.TypeId == "App::DocumentObjectGroup": continue # we ignore groups. + # We ignore dynamic sub-assemblies. + if obj.isDerivedFrom("Assembly::AssemblyLink") and obj.Rigid == False: + continue + # If it is a LinkGroup then we skip it if isLinkGroup(obj): continue