| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | #include <cmath> |
| | #include <vector> |
| |
|
| |
|
| | #include <App/Application.h> |
| | #include <App/Document.h> |
| | #include <App/DocumentObjectGroup.h> |
| | #include <App/FeaturePythonPyImp.h> |
| | #include <App/Link.h> |
| | #include <App/PropertyPythonObject.h> |
| | #include <Base/Console.h> |
| | #include <Base/Placement.h> |
| | #include <Base/Rotation.h> |
| | #include <Base/Tools.h> |
| | #include <Base/Interpreter.h> |
| |
|
| | #include <Mod/Part/App/PartFeature.h> |
| | #include <Mod/Part/App/TopoShape.h> |
| | #include <Mod/PartDesign/App/Body.h> |
| | #include <Mod/Part/App/DatumFeature.h> |
| |
|
| | #include "AssemblyObject.h" |
| | #include "AssemblyUtils.h" |
| | #include "JointGroup.h" |
| |
|
| | #include "AssemblyLink.h" |
| | #include "AssemblyLinkPy.h" |
| |
|
| | namespace PartApp = Part; |
| |
|
| | using namespace Assembly; |
| |
|
| | |
| |
|
| | 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())) { |
| | |
| | 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; |
| |
|
| | |
| | |
| | auto groundedJoints = getParentAssembly()->getGroundedJoints(); |
| | for (auto* joint : groundedJoints) { |
| | auto* propObj = dynamic_cast<App::PropertyLink*>( |
| | joint->getPropertyByName("ObjectToGround") |
| | ); |
| | if (!propObj) { |
| | continue; |
| | } |
| | auto* groundedObj = propObj->getValue(); |
| | if (auto* linkElt = dynamic_cast<App::LinkElement*>(groundedObj)) { |
| | |
| | groundedObj = linkElt->getLinkGroup(); |
| | } |
| |
|
| | if (Rigid.getValue() ? hasObject(groundedObj) : groundedObj == this) { |
| | getDocument()->removeObject(joint->getNameInDocument()); |
| | } |
| | } |
| |
|
| | if (Rigid.getValue()) { |
| | |
| | App::DocumentObject* firstLink = nullptr; |
| | for (auto* obj : Group.getValues()) { |
| | if (obj && (obj->isDerivedFrom<App::Link>() || obj->isDerivedFrom<AssemblyLink>())) { |
| | firstLink = obj; |
| | break; |
| | } |
| | } |
| |
|
| | if (firstLink) { |
| | App::DocumentObject* sourceObj = nullptr; |
| | if (auto* link = dynamic_cast<App::Link*>(firstLink)) { |
| | sourceObj = link->getLinkedObject(false); |
| | } |
| | else if (auto* asmLink = dynamic_cast<AssemblyLink*>(firstLink)) { |
| | sourceObj = asmLink->getLinkedAssembly(); |
| | } |
| |
|
| | if (sourceObj) { |
| | auto* propSource = dynamic_cast<App::PropertyPlacement*>( |
| | sourceObj->getPropertyByName("Placement") |
| | ); |
| | auto* propLink = dynamic_cast<App::PropertyPlacement*>( |
| | firstLink->getPropertyByName("Placement") |
| | ); |
| |
|
| | if (propSource && propLink) { |
| | movePlc = propLink->getValue() * propSource->getValue().inverse(); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | updateContents(); |
| |
|
| | auto* propPlc = dynamic_cast<App::PropertyPlacement*>(getPropertyByName("Placement")); |
| | if (!propPlc) { |
| | return; |
| | } |
| |
|
| | if (!Rigid.getValue()) { |
| | |
| | |
| | Base::Placement plc = propPlc->getValue(); |
| | if (!plc.isIdentity()) { |
| | propPlc->setValue(Base::Placement()); |
| |
|
| | |
| | |
| | std::vector<App::DocumentObject*> group = Group.getValues(); |
| | for (auto* obj : group) { |
| | if (!obj->isDerivedFrom<App::Part>() && !obj->isDerivedFrom<PartApp::Feature>() |
| | && !obj->isDerivedFrom<App::Link>()) { |
| | continue; |
| | } |
| |
|
| | if (obj->isLinkGroup()) { |
| | auto* srcLink = static_cast<App::Link*>(obj); |
| | const std::vector<App::DocumentObject*> srcElements |
| | = srcLink->ElementList.getValues(); |
| |
|
| | for (auto elt : srcElements) { |
| | if (!elt) { |
| | continue; |
| | } |
| |
|
| | auto* prop = dynamic_cast<App::PropertyPlacement*>( |
| | elt->getPropertyByName("Placement") |
| | ); |
| | if (prop) { |
| | prop->setValue(plc * prop->getValue()); |
| | } |
| | } |
| | } |
| | else { |
| | auto* prop = dynamic_cast<App::PropertyPlacement*>( |
| | obj->getPropertyByName("Placement") |
| | ); |
| | if (prop) { |
| | prop->setValue(plc * prop->getValue()); |
| | } |
| | } |
| | } |
| |
|
| | AssemblyObject::redrawJointPlacements(getJoints()); |
| | } |
| | } |
| | else { |
| | |
| | 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<App::DocumentObject*> assemblyGroup = assembly->Group.getValues(); |
| | std::vector<App::DocumentObject*> assemblyLinkGroup = Group.getValues(); |
| |
|
| | |
| | |
| | |
| | std::set<App::DocumentObject*> children; |
| | for (auto* obj : assemblyGroup) { |
| | if (auto* partFeat = dynamic_cast<PartApp::Feature*>(obj)) { |
| | if (auto* prop = dynamic_cast<App::PropertyLink*>(partFeat->getPropertyByName("Base"))) { |
| | if (prop->getValue()) { |
| | children.insert(prop->getValue()); |
| | } |
| | } |
| | if (auto* prop = dynamic_cast<App::PropertyLink*>(partFeat->getPropertyByName("Tool"))) { |
| | if (prop->getValue()) { |
| | children.insert(prop->getValue()); |
| | } |
| | } |
| | if (auto* prop |
| | = dynamic_cast<App::PropertyLinkList*>(partFeat->getPropertyByName("Shapes"))) { |
| | for (auto* shapeObj : prop->getValues()) { |
| | children.insert(shapeObj); |
| | } |
| | } |
| | } |
| | } |
| |
|
| | std::vector<App::DocumentObject*> topLevelComponents; |
| | std::copy_if( |
| | assemblyGroup.begin(), |
| | assemblyGroup.end(), |
| | std::back_inserter(topLevelComponents), |
| | [&children](App::DocumentObject* obj) { return children.find(obj) == children.end(); } |
| | ); |
| |
|
| | |
| | for (auto* obj : topLevelComponents) { |
| | if (!obj->isDerivedFrom<App::Part>() && !obj->isDerivedFrom<PartApp::Feature>() |
| | && !obj->isDerivedFrom<App::Link>()) { |
| | continue; |
| | } |
| |
|
| | |
| | |
| | App::DocumentObject* link = nullptr; |
| | bool found = false; |
| | std::set<App::Link*> linkGroupsAdded; |
| |
|
| | for (auto* obj2 : assemblyLinkGroup) { |
| | App::DocumentObject* linkedObj; |
| |
|
| | auto* subAsmLink = freecad_cast<AssemblyLink*>(obj2); |
| | auto* link2 = dynamic_cast<App::Link*>(obj2); |
| |
|
| | if (subAsmLink) { |
| | linkedObj = subAsmLink->getLinkedObject2(false); |
| | } |
| | else if (link2) { |
| | if (obj->isLinkGroup() && link2->isLinkGroup()) { |
| | auto* srcLink = static_cast<App::Link*>(obj); |
| | if ((srcLink->getTrueLinkedObject(false) == link2->getTrueLinkedObject(false)) |
| | && link2->ElementCount.getValue() == srcLink->ElementCount.getValue() |
| | && linkGroupsAdded.find(srcLink) == linkGroupsAdded.end()) { |
| | found = true; |
| | link = obj2; |
| | |
| | |
| | linkGroupsAdded.insert(srcLink); |
| |
|
| | const std::vector<App::DocumentObject*> srcElements |
| | = srcLink->ElementList.getValues(); |
| | const std::vector<App::DocumentObject*> newElements |
| | = link2->ElementList.getValues(); |
| | for (size_t i = 0; i < srcElements.size(); ++i) { |
| | objLinkMap[srcElements[i]] = newElements[i]; |
| | } |
| | break; |
| | } |
| | } |
| | else if (obj->isLinkGroup() && !link2->isLinkGroup()) { |
| | continue; |
| | } |
| | linkedObj = link2->getLinkedObject(false); |
| | } |
| | else { |
| | |
| | continue; |
| | } |
| |
|
| | if (linkedObj == obj) { |
| | found = true; |
| | link = obj2; |
| | break; |
| | } |
| | } |
| | if (!found) { |
| | |
| | if (obj->isDerivedFrom<AssemblyLink>()) { |
| | auto* asmLink = static_cast<AssemblyLink*>(obj); |
| |
|
| | App::DocumentObject* newObj |
| | = doc->addObject("Assembly::AssemblyLink", obj->getNameInDocument()); |
| | auto* subAsmLink = static_cast<AssemblyLink*>(newObj); |
| | subAsmLink->LinkedObject.setValue(obj); |
| | subAsmLink->Rigid.setValue(asmLink->Rigid.getValue()); |
| | subAsmLink->Label.setValue(obj->Label.getValue()); |
| | addObject(subAsmLink); |
| | link = subAsmLink; |
| | } |
| | else if (obj->isDerivedFrom<App::Link>() && obj->isLinkGroup()) { |
| | auto* srcLink = static_cast<App::Link*>(obj); |
| |
|
| | auto* newLink = static_cast<App::Link*>( |
| | doc->addObject("App::Link", obj->getNameInDocument()) |
| | ); |
| | newLink->LinkedObject.setValue(srcLink->getTrueLinkedObject(false)); |
| |
|
| | newLink->Label.setValue(obj->Label.getValue()); |
| | addObject(newLink); |
| |
|
| | newLink->ElementCount.setValue(srcLink->ElementCount.getValue()); |
| | const std::vector<App::DocumentObject*> srcElements = srcLink->ElementList.getValues(); |
| | const std::vector<App::DocumentObject*> newElements = newLink->ElementList.getValues(); |
| | for (size_t i = 0; i < srcElements.size(); ++i) { |
| | auto* newObj = newElements[i]; |
| | auto* srcObj = srcElements[i]; |
| | if (newObj && srcObj) { |
| | syncPlacements(srcObj, newObj); |
| | } |
| | objLinkMap[srcObj] = newObj; |
| | } |
| |
|
| | link = newLink; |
| | } |
| | else { |
| | App::DocumentObject* newObj = doc->addObject("App::Link", obj->getNameInDocument()); |
| | auto* newLink = static_cast<App::Link*>(newObj); |
| | newLink->LinkedObject.setValue(obj); |
| | newLink->Label.setValue(obj->Label.getValue()); |
| | addObject(newLink); |
| | link = newLink; |
| | } |
| | } |
| |
|
| | objLinkMap[obj] = link; |
| | } |
| |
|
| | |
| | if (isRigid()) { |
| | for (const auto& [sourceObj, linkObj] : objLinkMap) { |
| | syncPlacements(sourceObj, linkObj); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | std::set<App::DocumentObject*> validLinks; |
| | for (const auto& pair : objLinkMap) { |
| | validLinks.insert(pair.second); |
| | } |
| | for (auto* obj : assemblyLinkGroup) { |
| | |
| | |
| | if (!obj->isDerivedFrom<App::Part>() && !obj->isDerivedFrom<PartApp::Feature>() |
| | && !obj->isDerivedFrom<App::Link>()) { |
| | continue; |
| | } |
| | if (validLinks.find(obj) == validLinks.end()) { |
| | doc->removeObject(obj->getNameInDocument()); |
| | } |
| | } |
| | } |
| |
|
| | namespace |
| | { |
| | template<typename T> |
| | void copyPropertyIfDifferent( |
| | App::DocumentObject* source, |
| | App::DocumentObject* target, |
| | const char* propertyName |
| | ) |
| | { |
| | auto sourceProp = freecad_cast<T*>(source->getPropertyByName(propertyName)); |
| | auto targetProp = freecad_cast<T*>(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) { |
| | |
| | pos += name.length() + 1; |
| | if (pos < sub.length()) { |
| | return sub.substr(pos); |
| | } |
| | } |
| | |
| | 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; |
| | } |
| | }; |
| |
|
| | void AssemblyLink::synchronizeJoints() |
| | { |
| | App::Document* doc = getDocument(); |
| | AssemblyObject* assembly = getLinkedAssembly(); |
| | if (!assembly) { |
| | return; |
| | } |
| |
|
| | JointGroup* jGroup = ensureJointGroup(); |
| |
|
| | std::vector<App::DocumentObject*> assemblyJoints |
| | = assembly->getJoints(assembly->isTouched(), false, false); |
| | std::vector<App::DocumentObject*> assemblyLinkJoints = getJoints(); |
| |
|
| | |
| | for (size_t i = assemblyJoints.size(); i < assemblyLinkJoints.size(); ++i) { |
| | doc->removeObject(assemblyLinkJoints[i]->getNameInDocument()); |
| | } |
| |
|
| | |
| | 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); |
| | } |
| |
|
| | |
| | copyPropertyIfDifferent<App::PropertyBool>(joint, lJoint, "Suppressed"); |
| | copyPropertyIfDifferent<App::PropertyFloat>(joint, lJoint, "Distance"); |
| | copyPropertyIfDifferent<App::PropertyFloat>(joint, lJoint, "Distance2"); |
| | copyPropertyIfDifferent<App::PropertyEnumeration>(joint, lJoint, "JointType"); |
| | copyPropertyIfDifferent<App::PropertyPlacement>(joint, lJoint, "Offset1"); |
| | copyPropertyIfDifferent<App::PropertyPlacement>(joint, lJoint, "Offset2"); |
| |
|
| | copyPropertyIfDifferent<App::PropertyBool>(joint, lJoint, "Detach1"); |
| | copyPropertyIfDifferent<App::PropertyBool>(joint, lJoint, "Detach2"); |
| |
|
| | copyPropertyIfDifferent<App::PropertyFloat>(joint, lJoint, "AngleMax"); |
| | copyPropertyIfDifferent<App::PropertyFloat>(joint, lJoint, "AngleMin"); |
| | copyPropertyIfDifferent<App::PropertyFloat>(joint, lJoint, "LengthMax"); |
| | copyPropertyIfDifferent<App::PropertyFloat>(joint, lJoint, "LengthMin"); |
| | copyPropertyIfDifferent<App::PropertyBool>(joint, lJoint, "EnableAngleMax"); |
| | copyPropertyIfDifferent<App::PropertyBool>(joint, lJoint, "EnableAngleMin"); |
| | copyPropertyIfDifferent<App::PropertyBool>(joint, lJoint, "EnableLengthMax"); |
| | copyPropertyIfDifferent<App::PropertyBool>(joint, lJoint, "EnableLengthMin"); |
| |
|
| | |
| | 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<App::PropertyXLinkSubHidden*>(joint->getPropertyByName(refName)); |
| | auto prop2 = dynamic_cast<App::PropertyXLinkSubHidden*>(lJoint->getPropertyByName(refName)); |
| | if (!prop1 || !prop2) { |
| | return; |
| | } |
| |
|
| | App::DocumentObject* obj1 = nullptr; |
| | App::DocumentObject* obj2 = prop2->getValue(); |
| | std::vector<std::string> subs1 = prop1->getSubValues(); |
| | std::vector<std::string> subs2 = prop2->getSubValues(); |
| | if (subs1.empty()) { |
| | return; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | std::string asmLink = getNameInDocument(); |
| | for (auto& sub : subs1) { |
| | |
| | sub = removeUpToName(sub, assembly->getNameInDocument()); |
| | |
| | sub = asmLink + "." + sub; |
| | |
| | |
| | bool first = true; |
| | std::vector<App::DocumentObject*> inList = getInList(); |
| | int limit = 0; |
| | while (!inList.empty() && limit < 20) { |
| | ++limit; |
| | bool found = false; |
| | for (auto* obj : inList) { |
| | if (obj->isDerivedFrom<App::Part>()) { |
| | 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 = {}; |
| | } |
| | } |
| |
|
| | |
| | auto* obj = 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); |
| | } |
| | |
| | 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() |
| | { |
| | |
| | JointGroup* jGroup = getJointGroup(this); |
| | if (jGroup) { |
| | |
| | jGroup->removeObjectsFromDocument(); |
| | getDocument()->removeObject(jGroup->getNameInDocument()); |
| | } |
| | } |
| | JointGroup* AssemblyLink::ensureJointGroup() |
| | { |
| | |
| | JointGroup* jGroup = getJointGroup(this); |
| | if (!jGroup) { |
| | jGroup = new JointGroup(); |
| | getDocument()->addObject(jGroup, tr("Joints").toStdString().c_str()); |
| |
|
| | std::vector<DocumentObject*> 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 = freecad_cast<AssemblyObject*>(obj); |
| | if (assembly) { |
| | return assembly; |
| | } |
| | else { |
| | auto* assemblyLink = freecad_cast<AssemblyLink*>(obj); |
| | if (assemblyLink) { |
| | if (recursive) { |
| | return assemblyLink->getLinkedObject2(recursive); |
| | } |
| | else { |
| | return assemblyLink; |
| | } |
| | } |
| | } |
| |
|
| | return nullptr; |
| | } |
| |
|
| | AssemblyObject* AssemblyLink::getLinkedAssembly() const |
| | { |
| | return freecad_cast<AssemblyObject*>(getLinkedObject2()); |
| | } |
| |
|
| | AssemblyObject* AssemblyLink::getParentAssembly() const |
| | { |
| | std::vector<App::DocumentObject*> inList = getInList(); |
| | for (auto* obj : inList) { |
| | auto* assembly = freecad_cast<AssemblyObject*>(obj); |
| | if (assembly) { |
| | return assembly; |
| | } |
| | } |
| |
|
| | return nullptr; |
| | } |
| |
|
| | bool AssemblyLink::isRigid() const |
| | { |
| | auto* prop = dynamic_cast<App::PropertyBool*>(getPropertyByName("Rigid")); |
| | if (!prop) { |
| | return true; |
| | } |
| | return prop->getValue(); |
| | } |
| |
|
| | std::vector<App::DocumentObject*> AssemblyLink::getJoints() |
| | { |
| | JointGroup* jointGroup = getJointGroup(this); |
| |
|
| | if (!jointGroup) { |
| | return {}; |
| | } |
| | return jointGroup->getJoints(); |
| | } |
| |
|
| | bool AssemblyLink::allowDuplicateLabel() const |
| | { |
| | return true; |
| | } |
| |
|
| | int AssemblyLink::numberOfComponents() const |
| | { |
| | return isRigid() ? 1 : getLinkedAssembly()->numberOfComponents(); |
| | } |
| |
|
| | bool AssemblyLink::isEmpty() const |
| | { |
| | return numberOfComponents() == 0; |
| | } |
| |
|