// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * * * Copyright (c) 2023 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 #include #include #include #include #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "AssemblyObject.h" #include "AssemblyObjectPy.h" #include "JointGroup.h" namespace PartApp = Part; using namespace Assembly; using namespace MbD; // ================================ Assembly Object ============================ PROPERTY_SOURCE(Assembly::AssemblyObject, App::Part) AssemblyObject::AssemblyObject() : mbdAssembly(std::make_shared()) {} AssemblyObject::~AssemblyObject() = default; PyObject* AssemblyObject::getPyObject() { if (PythonObject.is(Py::_None())) { // ref counter is set to 1 PythonObject = Py::Object(new AssemblyObjectPy(this), true); } return Py::new_reference_to(PythonObject); } int AssemblyObject::solve(bool enableRedo) { mbdAssembly = makeMbdAssembly(); objectPartMap.clear(); std::vector groundedObjs = fixGroundedParts(); if (groundedObjs.empty()) { // If no part fixed we can't solve. return -6; } std::vector joints = getJoints(); removeUnconnectedJoints(joints, groundedObjs); jointParts(joints); if (enableRedo) { savePlacementsForUndo(); } try { mbdAssembly->solve(); } catch (...) { Base::Console().Error("Solve failed\n"); return -1; } setNewPlacements(); redrawJointPlacements(joints); return 0; } void AssemblyObject::preDrag(std::vector dragParts) { solve(); dragMbdParts.clear(); for (auto part : dragParts) { dragMbdParts.push_back(getMbDPart(part)); } mbdAssembly->runPreDrag(); } void AssemblyObject::doDragStep() { for (auto& mbdPart : dragMbdParts) { App::DocumentObject* part = nullptr; for (auto& pair : objectPartMap) { if (pair.second == mbdPart) { part = pair.first; break; } } if (!part) { continue; } Base::Placement plc = getPlacementFromProp(part, "Placement"); Base::Vector3d pos = plc.getPosition(); mbdPart->setPosition3D(pos.x, pos.y, pos.z); Base::Rotation rot = plc.getRotation(); Base::Matrix4D mat; rot.getValue(mat); Base::Vector3d r0 = mat.getRow(0); Base::Vector3d r1 = mat.getRow(1); Base::Vector3d r2 = mat.getRow(2); mbdPart->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z); } auto dragPartsVec = std::make_shared>>(dragMbdParts); mbdAssembly->runDragStep(dragPartsVec); setNewPlacements(); redrawJointPlacements(getJoints()); } void AssemblyObject::postDrag() { mbdAssembly->runPostDrag(); // Do this after last drag } void AssemblyObject::savePlacementsForUndo() { previousPositions.clear(); for (auto& pair : objectPartMap) { App::DocumentObject* obj = pair.first; if (!obj) { continue; } std::pair savePair; savePair.first = obj; // Check if the object has a "Placement" property auto* propPlc = dynamic_cast(obj->getPropertyByName("Placement")); if (!propPlc) { continue; } savePair.second = propPlc->getValue(); previousPositions.push_back(savePair); } } void AssemblyObject::undoSolve() { if (previousPositions.size() == 0) { return; } for (auto& pair : previousPositions) { App::DocumentObject* obj = pair.first; if (!obj) { continue; } // Check if the object has a "Placement" property auto* propPlacement = dynamic_cast(obj->getPropertyByName("Placement")); if (!propPlacement) { continue; } propPlacement->setValue(pair.second); } previousPositions.clear(); // update joint placements: getJoints(); } void AssemblyObject::clearUndo() { previousPositions.clear(); } void AssemblyObject::exportAsASMT(std::string fileName) { mbdAssembly = makeMbdAssembly(); objectPartMap.clear(); fixGroundedParts(); std::vector joints = getJoints(); jointParts(joints); mbdAssembly->outputFile(fileName); } void AssemblyObject::setNewPlacements() { for (auto& pair : objectPartMap) { App::DocumentObject* obj = pair.first; std::shared_ptr mbdPart = pair.second; if (!obj || !mbdPart) { continue; } // Check if the object has a "Placement" property auto* propPlacement = dynamic_cast(obj->getPropertyByName("Placement")); if (!propPlacement) { continue; } double x, y, z; mbdPart->getPosition3D(x, y, z); // Base::Console().Warning("in set placement : (%f, %f, %f)\n", x, y, z); Base::Vector3d pos = Base::Vector3d(x, y, z); // TODO : replace with quaternion to simplify auto& r0 = mbdPart->rotationMatrix->at(0); auto& r1 = mbdPart->rotationMatrix->at(1); auto& r2 = mbdPart->rotationMatrix->at(2); Base::Vector3d row0 = Base::Vector3d(r0->at(0), r0->at(1), r0->at(2)); Base::Vector3d row1 = Base::Vector3d(r1->at(0), r1->at(1), r1->at(2)); Base::Vector3d row2 = Base::Vector3d(r2->at(0), r2->at(1), r2->at(2)); Base::Matrix4D mat; mat.setRow(0, row0); mat.setRow(1, row1); mat.setRow(2, row2); Base::Rotation rot = Base::Rotation(mat); /*double q0, q1, q2, q3; mbdPart->getQuarternions(q0, q1, q2, q3); Base::Rotation rot = Base::Rotation(q0, q1, q2, q3);*/ Base::Placement newPlacement = Base::Placement(pos, rot); propPlacement->setValue(newPlacement); } } void AssemblyObject::redrawJointPlacements(std::vector joints) { // Notify the joint objects that the transform of the coin object changed. for (auto* joint : joints) { auto* propPlacement = dynamic_cast(joint->getPropertyByName("Placement1")); if (propPlacement) { propPlacement->setValue(propPlacement->getValue()); } propPlacement = dynamic_cast(joint->getPropertyByName("Placement2")); if (propPlacement) { propPlacement->setValue(propPlacement->getValue()); } } } void AssemblyObject::recomputeJointPlacements(std::vector joints) { // The Placement1 and Placement2 of each joint needs to be updated as the parts moved. for (auto* joint : joints) { App::PropertyPythonObject* proxy = joint ? dynamic_cast(joint->getPropertyByName("Proxy")) : nullptr; if (!proxy) { continue; } Py::Object jointPy = proxy->getValue(); if (!jointPy.hasAttr("updateJCSPlacements")) { continue; } Py::Object attr = jointPy.getAttr("updateJCSPlacements"); if (attr.ptr() && attr.isCallable()) { Py::Tuple args(1); args.setItem(0, Py::asObject(joint->getPyObject())); Py::Callable(attr).apply(args); } } } std::shared_ptr AssemblyObject::makeMbdAssembly() { auto assembly = CREATE::With(); assembly->setName("OndselAssembly"); return assembly; } App::DocumentObject* AssemblyObject::getJointOfPartConnectingToGround(App::DocumentObject* part, std::string& name) { std::vector joints = getJointsOfPart(part); for (auto joint : joints) { if (!joint) { continue; } App::DocumentObject* part1 = getLinkObjFromProp(joint, "Part1"); App::DocumentObject* part2 = getLinkObjFromProp(joint, "Part2"); if (!part1 || !part2) { continue; } if (part == part1 && isJointConnectingPartToGround(joint, "Part1")) { name = "Part1"; return joint; } if (part == part2 && isJointConnectingPartToGround(joint, "Part2")) { name = "Part2"; return joint; } } return nullptr; } JointGroup* AssemblyObject::getJointGroup() { App::Document* doc = getDocument(); std::vector jointGroups = doc->getObjectsOfType(Assembly::JointGroup::getClassTypeId()); if (jointGroups.empty()) { return nullptr; } for (auto jointGroup : jointGroups) { if (hasObject(jointGroup)) { return dynamic_cast(jointGroup); } } return nullptr; } std::vector AssemblyObject::getJoints(bool updateJCS) { std::vector joints = {}; JointGroup* jointGroup = getJointGroup(); if (!jointGroup) { return {}; } Base::PyGILStateLocker lock; for (auto joint : jointGroup->getObjects()) { if (!joint) { continue; } auto* prop = dynamic_cast(joint->getPropertyByName("Activated")); if (prop && !prop->getValue()) { continue; } auto proxy = dynamic_cast(joint->getPropertyByName("Proxy")); if (proxy) { if (proxy->getValue().hasAttr("setJointConnectors")) { joints.push_back(joint); } } } // Make sure the joints are up to date. if (updateJCS) { recomputeJointPlacements(joints); } return joints; } std::vector AssemblyObject::getGroundedJoints() { std::vector joints = {}; JointGroup* jointGroup = getJointGroup(); if (!jointGroup) { return {}; } Base::PyGILStateLocker lock; for (auto obj : jointGroup->getObjects()) { if (!obj) { continue; } auto* propObj = dynamic_cast(obj->getPropertyByName("ObjectToGround")); if (propObj) { joints.push_back(obj); } } return joints; } std::vector AssemblyObject::getJointsOfObj(App::DocumentObject* obj) { std::vector joints = getJoints(false); std::vector jointsOf; for (auto joint : joints) { App::DocumentObject* obj1 = getObjFromNameProp(joint, "object1", "Part1"); App::DocumentObject* obj2 = getObjFromNameProp(joint, "Object2", "Part2"); if (obj == obj1 || obj == obj2) { jointsOf.push_back(obj); } } return jointsOf; } std::vector AssemblyObject::getJointsOfPart(App::DocumentObject* part) { std::vector joints = getJoints(false); std::vector jointsOf; for (auto joint : joints) { App::DocumentObject* part1 = getLinkObjFromProp(joint, "Part1"); App::DocumentObject* part2 = getLinkObjFromProp(joint, "Part2"); if (part == part1 || part == part2) { jointsOf.push_back(joint); } } return jointsOf; } std::vector AssemblyObject::getGroundedParts() { std::vector groundedJoints = getGroundedJoints(); std::vector groundedObjs; for (auto gJoint : groundedJoints) { if (!gJoint) { continue; } auto* propObj = dynamic_cast(gJoint->getPropertyByName("ObjectToGround")); if (propObj) { App::DocumentObject* objToGround = propObj->getValue(); groundedObjs.push_back(objToGround); } } return groundedObjs; } std::vector AssemblyObject::fixGroundedParts() { std::vector groundedJoints = getGroundedJoints(); std::vector groundedObjs; for (auto obj : groundedJoints) { if (!obj) { continue; } auto* propObj = dynamic_cast(obj->getPropertyByName("ObjectToGround")); if (propObj) { App::DocumentObject* objToGround = propObj->getValue(); Base::Placement plc = getPlacementFromProp(obj, "Placement"); std::string str = obj->getFullName(); fixGroundedPart(objToGround, plc, str); groundedObjs.push_back(objToGround); } } return groundedObjs; } void AssemblyObject::fixGroundedPart(App::DocumentObject* obj, Base::Placement& plc, std::string& name) { std::string markerName1 = "marker-" + obj->getFullName(); auto mbdMarker1 = makeMbdMarker(markerName1, plc); mbdAssembly->addMarker(mbdMarker1); std::shared_ptr mbdPart = getMbDPart(obj); std::string markerName2 = "FixingMarker"; Base::Placement basePlc = Base::Placement(); auto mbdMarker2 = makeMbdMarker(markerName2, basePlc); mbdPart->addMarker(mbdMarker2); markerName1 = "/OndselAssembly/" + mbdMarker1->name; markerName2 = "/OndselAssembly/" + mbdPart->name + "/" + mbdMarker2->name; auto mbdJoint = CREATE::With(); mbdJoint->setName(name); mbdJoint->setMarkerI(markerName1); mbdJoint->setMarkerJ(markerName2); mbdAssembly->addJoint(mbdJoint); } bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, const char* propname) { auto* propPart = dynamic_cast(joint->getPropertyByName(propname)); if (!propPart) { return false; } App::DocumentObject* part = propPart->getValue(); // Check if the part is disconnected even with the joint bool isConnected = isPartConnected(part); if (!isConnected) { return false; } // to know if a joint is connecting to ground we disable all the other joints std::vector jointsOfPart = getJointsOfPart(part); std::vector activatedStates; for (auto jointi : jointsOfPart) { if (jointi->getFullName() == joint->getFullName()) { continue; } activatedStates.push_back(getJointActivated(jointi)); setJointActivated(jointi, false); } isConnected = isPartConnected(part); // restore activation states for (auto jointi : jointsOfPart) { if (jointi->getFullName() == joint->getFullName() || activatedStates.empty()) { continue; } setJointActivated(jointi, activatedStates[0]); activatedStates.erase(activatedStates.begin()); } return isConnected; } void AssemblyObject::removeUnconnectedJoints(std::vector& joints, std::vector groundedObjs) { std::set connectedParts; // Initialize connectedParts with groundedObjs for (auto* groundedObj : groundedObjs) { connectedParts.insert(groundedObj); } // Perform a traversal from each grounded object for (auto* groundedObj : groundedObjs) { traverseAndMarkConnectedParts(groundedObj, connectedParts, joints); } // Filter out unconnected joints joints.erase( std::remove_if( joints.begin(), joints.end(), [&connectedParts](App::DocumentObject* joint) { App::DocumentObject* obj1 = getLinkObjFromProp(joint, "Part1"); App::DocumentObject* obj2 = getLinkObjFromProp(joint, "Part2"); if ((connectedParts.find(obj1) == connectedParts.end()) || (connectedParts.find(obj2) == connectedParts.end())) { Base::Console().Warning( "%s is unconnected to a grounded part so it is ignored.\n", joint->getFullName()); return true; // Remove joint if any connected object is not in connectedParts } return false; }), joints.end()); } void AssemblyObject::traverseAndMarkConnectedParts(App::DocumentObject* currentObj, std::set& connectedParts, const std::vector& joints) { // getConnectedParts returns the objs connected to the currentObj by any joint auto connectedObjs = getConnectedParts(currentObj, joints); for (auto* nextObj : connectedObjs) { if (connectedParts.find(nextObj) == connectedParts.end()) { connectedParts.insert(nextObj); traverseAndMarkConnectedParts(nextObj, connectedParts, joints); } } } std::vector AssemblyObject::getConnectedParts(App::DocumentObject* part, const std::vector& joints) { std::vector connectedParts; for (auto joint : joints) { App::DocumentObject* obj1 = getLinkObjFromProp(joint, "Part1"); App::DocumentObject* obj2 = getLinkObjFromProp(joint, "Part2"); if (obj1 == part) { connectedParts.push_back(obj2); } else if (obj2 == part) { connectedParts.push_back(obj1); } } return connectedParts; } bool AssemblyObject::isPartGrounded(App::DocumentObject* obj) { std::vector groundedObjs = fixGroundedParts(); for (auto* groundedObj : groundedObjs) { if (groundedObj->getFullName() == obj->getFullName()) { return true; } } return false; } bool AssemblyObject::isPartConnected(App::DocumentObject* obj) { std::vector groundedObjs = getGroundedParts(); std::vector joints = getJoints(false); std::set connectedParts; // Initialize connectedParts with groundedObjs for (auto* groundedObj : groundedObjs) { connectedParts.insert(groundedObj); } // Perform a traversal from each grounded object for (auto* groundedObj : groundedObjs) { traverseAndMarkConnectedParts(groundedObj, connectedParts, joints); } for (auto part : connectedParts) { if (obj == part) { return true; } } return false; } void AssemblyObject::jointParts(std::vector joints) { for (auto* joint : joints) { if (!joint) { continue; } std::vector> mbdJoints = makeMbdJoint(joint); for (auto& mbdJoint : mbdJoints) { mbdAssembly->addJoint(mbdJoint); } } } std::shared_ptr AssemblyObject::makeMbdJointOfType(App::DocumentObject* joint, JointType type) { if (type == JointType::Fixed) { return CREATE::With(); } else if (type == JointType::Revolute) { return CREATE::With(); } else if (type == JointType::Cylindrical) { return CREATE::With(); } else if (type == JointType::Slider) { return CREATE::With(); } else if (type == JointType::Ball) { return CREATE::With(); } else if (type == JointType::Distance) { return makeMbdJointDistance(joint); } return nullptr; } std::shared_ptr AssemblyObject::makeMbdJointDistance(App::DocumentObject* joint) { // Depending on the type of element of the JCS, we apply the correct set of constraints. std::string type1 = getElementTypeFromProp(joint, "Element1"); std::string type2 = getElementTypeFromProp(joint, "Element2"); if (type1 == "Vertex" && type2 == "Vertex") { // Point to point distance, or ball joint if distance=0. auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint); return mbdJoint; } else if (type1 == "Edge" && type2 == "Edge") { return makeMbdJointDistanceEdgeEdge(joint); } else if (type1 == "Face" && type2 == "Face") { return makeMbdJointDistanceFaceFace(joint); } else if ((type1 == "Vertex" && type2 == "Face") || (type1 == "Face" && type2 == "Vertex")) { if (type1 == "Vertex") { // Make sure face is the first. swapJCS(joint); } return makeMbdJointDistanceFaceVertex(joint); } else if ((type1 == "Edge" && type2 == "Face") || (type1 == "Face" && type2 == "Edge")) { if (type1 == "Edge") { // Make sure face is the first. swapJCS(joint); } return makeMbdJointDistanceFaceEdge(joint); } else if ((type1 == "Vertex" && type2 == "Edge") || (type1 == "Edge" && type2 == "Vertex")) { if (type1 == "Vertex") { // Make sure edge is the first. swapJCS(joint); } return makeMbdJointDistanceEdgeVertex(joint); } return nullptr; } std::shared_ptr AssemblyObject::makeMbdJointDistanceEdgeEdge(App::DocumentObject* joint) { const char* elt1 = getElementFromProp(joint, "Element1"); const char* elt2 = getElementFromProp(joint, "Element2"); auto* obj1 = getLinkedObjFromNameProp(joint, "Object1", "Part1"); auto* obj2 = getLinkedObjFromNameProp(joint, "Object2", "Part2"); if (isEdgeType(obj1, elt1, GeomAbs_Line) || isEdgeType(obj2, elt2, GeomAbs_Line)) { if (!isEdgeType(obj1, elt1, GeomAbs_Line)) { swapJCS(joint); // make sure that line is first if not 2 lines. std::swap(elt1, elt2); std::swap(obj1, obj2); } if (isEdgeType(obj2, elt2, GeomAbs_Line)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint); return mbdJoint; } else if (isEdgeType(obj2, elt2, GeomAbs_Circle)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getEdgeRadius(obj2, elt2); return mbdJoint; } // TODO : other cases Ellipse, parabola, hyperbola... } else if (isEdgeType(obj1, elt1, GeomAbs_Circle) || isEdgeType(obj2, elt2, GeomAbs_Circle)) { if (!isEdgeType(obj1, elt1, GeomAbs_Circle)) { swapJCS(joint); // make sure that circle is first if not 2 lines. std::swap(elt1, elt2); std::swap(obj1, obj2); } if (isEdgeType(obj2, elt2, GeomAbs_Circle)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getEdgeRadius(obj1, elt1) + getEdgeRadius(obj2, elt2); return mbdJoint; } // TODO : other cases Ellipse, parabola, hyperbola... } // TODO : other cases Ellipse, parabola, hyperbola... return nullptr; } std::shared_ptr AssemblyObject::makeMbdJointDistanceFaceFace(App::DocumentObject* joint) { const char* elt1 = getElementFromProp(joint, "Element1"); const char* elt2 = getElementFromProp(joint, "Element2"); auto* obj1 = getLinkedObjFromNameProp(joint, "Object1", "Part1"); auto* obj2 = getLinkedObjFromNameProp(joint, "Object2", "Part2"); if (isFaceType(obj1, elt1, GeomAbs_Plane) || isFaceType(obj2, elt2, GeomAbs_Plane)) { if (!isFaceType(obj1, elt1, GeomAbs_Plane)) { swapJCS(joint); // make sure plane is first if its not 2 planes. std::swap(elt1, elt2); std::swap(obj1, obj2); } if (isFaceType(obj2, elt2, GeomAbs_Plane)) { auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } else if (isFaceType(obj2, elt2, GeomAbs_Cylinder)) { auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint) + getFaceRadius(obj2, elt2); return mbdJoint; } else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) { auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint) + getFaceRadius(obj2, elt2); return mbdJoint; } else if (isFaceType(obj2, elt2, GeomAbs_Cone)) { // TODO } else if (isFaceType(obj2, elt2, GeomAbs_Torus)) { auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } } else if (isFaceType(obj1, elt1, GeomAbs_Cylinder) || isFaceType(obj2, elt2, GeomAbs_Cylinder)) { if (!isFaceType(obj1, elt1, GeomAbs_Cylinder)) { swapJCS(joint); // make sure cylinder is first if its not 2 cylinders. std::swap(elt1, elt2); std::swap(obj1, obj2); } if (isFaceType(obj2, elt2, GeomAbs_Cylinder)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getFaceRadius(obj1, elt1) + getFaceRadius(obj2, elt2); return mbdJoint; } else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getFaceRadius(obj1, elt1) + getFaceRadius(obj2, elt2); return mbdJoint; } else if (isFaceType(obj2, elt2, GeomAbs_Cone)) { // TODO } else if (isFaceType(obj2, elt2, GeomAbs_Torus)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getFaceRadius(obj1, elt1) + getFaceRadius(obj2, elt2); return mbdJoint; } } else if (isFaceType(obj1, elt1, GeomAbs_Cone) || isFaceType(obj2, elt2, GeomAbs_Cone)) { if (!isFaceType(obj1, elt1, GeomAbs_Cone)) { swapJCS(joint); // make sure cone is first if its not 2 cones. std::swap(elt1, elt2); std::swap(obj1, obj2); } if (isFaceType(obj2, elt2, GeomAbs_Cone)) { // TODO } else if (isFaceType(obj2, elt2, GeomAbs_Torus)) { // TODO } else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) { // TODO } } else if (isFaceType(obj1, elt1, GeomAbs_Torus) || isFaceType(obj2, elt2, GeomAbs_Torus)) { if (!isFaceType(obj1, elt1, GeomAbs_Torus)) { swapJCS(joint); // make sure torus is first if its not 2 torus. std::swap(elt1, elt2); std::swap(obj1, obj2); } if (isFaceType(obj2, elt2, GeomAbs_Torus)) { auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getFaceRadius(obj1, elt1) + getFaceRadius(obj2, elt2); return mbdJoint; } } else if (isFaceType(obj1, elt1, GeomAbs_Sphere) || isFaceType(obj2, elt2, GeomAbs_Sphere)) { if (!isFaceType(obj1, elt1, GeomAbs_Sphere)) { swapJCS(joint); // make sure sphere is first if its not 2 spheres. std::swap(elt1, elt2); std::swap(obj1, obj2); } if (isFaceType(obj2, elt2, GeomAbs_Sphere)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getFaceRadius(obj1, elt1) + getFaceRadius(obj2, elt2); return mbdJoint; } } else { // by default we make a planar joint. auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } return nullptr; } std::shared_ptr AssemblyObject::makeMbdJointDistanceFaceVertex(App::DocumentObject* joint) { const char* elt1 = getElementFromProp(joint, "Element1"); auto* obj1 = getLinkedObjFromNameProp(joint, "Object1", "Part1"); if (isFaceType(obj1, elt1, GeomAbs_Plane)) { auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } else if (isFaceType(obj1, elt1, GeomAbs_Cylinder)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getFaceRadius(obj1, elt1); return mbdJoint; } else if (isFaceType(obj1, elt1, GeomAbs_Sphere)) { auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint) + getFaceRadius(obj1, elt1); return mbdJoint; } /*else if (isFaceType(obj1, elt1, GeomAbs_Cone)) { // TODO } else if (isFaceType(obj1, elt1, GeomAbs_Thorus)) { // TODO }*/ return nullptr; } std::shared_ptr AssemblyObject::makeMbdJointDistanceEdgeVertex(App::DocumentObject* joint) { const char* elt1 = getElementFromProp(joint, "Element1"); auto* obj1 = getLinkedObjFromNameProp(joint, "Object1", "Part1"); if (isEdgeType(obj1, elt1, GeomAbs_Line)) { // Point on line joint. auto mbdJoint = CREATE::With(); mbdJoint->distanceIJ = getJointDistance(joint); return mbdJoint; } else { // For other curves we do a point in plane-of-the-curve. // Maybe it would be best tangent / distance to the conic? // For arcs and circles we could use ASMTRevSphJoint. But is it better than pointInPlane? auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } return nullptr; } std::shared_ptr AssemblyObject::makeMbdJointDistanceFaceEdge(App::DocumentObject* joint) { const char* elt2 = getElementFromProp(joint, "Element2"); auto* obj2 = getLinkedObjFromNameProp(joint, "Object2", "Part2"); if (isEdgeType(obj2, elt2, GeomAbs_Line)) { // Make line in plane joint. auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } else { // planar joint for other edges. auto mbdJoint = CREATE::With(); mbdJoint->offset = getJointDistance(joint); return mbdJoint; } return nullptr; } std::vector> AssemblyObject::makeMbdJoint(App::DocumentObject* joint) { JointType jointType = getJointType(joint); std::shared_ptr mbdJoint = makeMbdJointOfType(joint, jointType); if (!mbdJoint) { return {}; } std::string fullMarkerName1 = handleOneSideOfJoint(joint, "Object1", "Part1", "Placement1"); std::string fullMarkerName2 = handleOneSideOfJoint(joint, "Object2", "Part2", "Placement2"); mbdJoint->setName(joint->getFullName()); mbdJoint->setMarkerI(fullMarkerName1); mbdJoint->setMarkerJ(fullMarkerName2); return {mbdJoint}; } std::string AssemblyObject::handleOneSideOfJoint(App::DocumentObject* joint, const char* propObjName, const char* propPartName, const char* propPlcName) { App::DocumentObject* part = getLinkObjFromProp(joint, propPartName); App::DocumentObject* obj = getObjFromNameProp(joint, propObjName, propPartName); std::shared_ptr mbdPart = getMbDPart(part); Base::Placement plc = getPlacementFromProp(joint, propPlcName); // Now we have plc which is the JCS placement, but its relative to the Object, not to the // containing Part. if (obj->getNameInDocument() != part->getNameInDocument()) { // Make plc relative to the containing part // plc = objPlc * plc; // this would not work for nested parts. Base::Placement obj_global_plc = getGlobalPlacement(obj, part); plc = obj_global_plc * plc; Base::Placement part_global_plc = getGlobalPlacement(part); plc = part_global_plc.inverse() * plc; } std::string markerName = joint->getFullName(); auto mbdMarker = makeMbdMarker(markerName, plc); mbdPart->addMarker(mbdMarker); return "/OndselAssembly/" + mbdPart->name + "/" + markerName; } std::shared_ptr AssemblyObject::getMbDPart(App::DocumentObject* obj) { std::shared_ptr mbdPart; Base::Placement plc = getPlacementFromProp(obj, "Placement"); auto it = objectPartMap.find(obj); if (it != objectPartMap.end()) { // obj has been associated with an ASMTPart before mbdPart = it->second; } else { // obj has not been associated with an ASMTPart before std::string str = obj->getFullName(); mbdPart = makeMbdPart(str, plc); mbdAssembly->addPart(mbdPart); objectPartMap[obj] = mbdPart; // Store the association } return mbdPart; } std::shared_ptr AssemblyObject::makeMbdPart(std::string& name, Base::Placement plc, double mass) { auto mbdPart = CREATE::With(); mbdPart->setName(name); auto massMarker = CREATE::With(); massMarker->setMass(mass); massMarker->setDensity(1.0); massMarker->setMomentOfInertias(1.0, 1.0, 1.0); mbdPart->setPrincipalMassMarker(massMarker); Base::Vector3d pos = plc.getPosition(); mbdPart->setPosition3D(pos.x, pos.y, pos.z); // Base::Console().Warning("MbD Part placement : (%f, %f, %f)\n", pos.x, pos.y, pos.z); // TODO : replace with quaternion to simplify Base::Rotation rot = plc.getRotation(); Base::Matrix4D mat; rot.getValue(mat); Base::Vector3d r0 = mat.getRow(0); Base::Vector3d r1 = mat.getRow(1); Base::Vector3d r2 = mat.getRow(2); mbdPart->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z); /*double q0, q1, q2, q3; rot.getValue(q0, q1, q2, q3); mbdPart->setQuarternions(q0, q1, q2, q3);*/ return mbdPart; } std::shared_ptr AssemblyObject::makeMbdMarker(std::string& name, Base::Placement& plc) { auto mbdMarker = CREATE::With(); mbdMarker->setName(name); Base::Vector3d pos = plc.getPosition(); mbdMarker->setPosition3D(pos.x, pos.y, pos.z); // TODO : replace with quaternion to simplify Base::Rotation rot = plc.getRotation(); Base::Matrix4D mat; rot.getValue(mat); Base::Vector3d r0 = mat.getRow(0); Base::Vector3d r1 = mat.getRow(1); Base::Vector3d r2 = mat.getRow(2); mbdMarker->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z); /*double q0, q1, q2, q3; rot.getValue(q0, q1, q2, q3); mbdMarker->setQuarternions(q0, q1, q2, q3);*/ return mbdMarker; } std::vector AssemblyObject::getDownstreamParts(App::DocumentObject* part, App::DocumentObject* joint) { // First we deactivate the joint bool state = getJointActivated(joint); setJointActivated(joint, false); std::vector joints = getJoints(false); std::set connectedParts = {part}; traverseAndMarkConnectedParts(part, connectedParts, joints); std::vector downstreamParts; for (auto parti : connectedParts) { if (!isPartConnected(parti) && (parti != part)) { downstreamParts.push_back(parti); } } AssemblyObject::setJointActivated(joint, state); /*if (limit > 1000) { // Inifinite loop protection return {}; } limit++; Base::Console().Warning("limit %d\n", limit); std::vector downstreamParts = {part}; std::string name; App::DocumentObject* connectingJoint = getJointOfPartConnectingToGround(part, name); // ?????????????????????????????? if we remove // connection to ground then it can't work for tom std::vector jointsOfPart = getJointsOfPart(part); // remove connectingJoint from jointsOfPart auto it = std::remove(jointsOfPart.begin(), jointsOfPart.end(), connectingJoint); jointsOfPart.erase(it, jointsOfPart.end()); for (auto joint : jointsOfPart) { App::DocumentObject* part1 = getLinkObjFromProp(joint, "Part1"); App::DocumentObject* part2 = getLinkObjFromProp(joint, "Part2"); bool firstIsDown = part->getFullName() == part2->getFullName(); App::DocumentObject* downstreamPart = firstIsDown ? part1 : part2; Base::Console().Warning("looping\n"); // it is possible that the part is connected to ground by this joint. // In which case we should not select those parts. To test we disconnect : auto* propObj = dynamic_cast(joint->getPropertyByName("Part1")); if (!propObj) { continue; } propObj->setValue(nullptr); bool isConnected = isPartConnected(downstreamPart); propObj->setValue(part1); if (isConnected) { Base::Console().Warning("continue\n"); continue; } std::vector subDownstreamParts = getDownstreamParts(downstreamPart, limit); for (auto downPart : subDownstreamParts) { if (std::find(downstreamParts.begin(), downstreamParts.end(), downPart) == downstreamParts.end()) { downstreamParts.push_back(downPart); } } }*/ return downstreamParts; } std::vector AssemblyObject::getUpstreamParts(App::DocumentObject* part, int limit) { if (limit > 1000) { // Inifinite loop protection return {}; } limit++; if (isPartGrounded(part)) { return {part}; } std::string name; App::DocumentObject* connectingJoint = getJointOfPartConnectingToGround(part, name); App::DocumentObject* upPart = getLinkObjFromProp(connectingJoint, name == "Part1" ? "Part2" : "Part1"); std::vector upstreamParts = getUpstreamParts(upPart, limit); upstreamParts.push_back(part); return upstreamParts; } App::DocumentObject* AssemblyObject::getUpstreamMovingPart(App::DocumentObject* part) { if (isPartGrounded(part)) { return nullptr; } std::string name; App::DocumentObject* connectingJoint = getJointOfPartConnectingToGround(part, name); JointType jointType = getJointType(connectingJoint); if (jointType != JointType::Fixed) { return part; } App::DocumentObject* upPart = getLinkObjFromProp(connectingJoint, name == "Part1" ? "Part2" : "Part1"); return getUpstreamMovingPart(upPart); } double AssemblyObject::getObjMass(App::DocumentObject* obj) { for (auto& pair : objMasses) { if (pair.first == obj) { return pair.second; } } return 1.0; } void AssemblyObject::setObjMasses(std::vector> objectMasses) { objMasses = objectMasses; } // ======================================= Utils ====================================== void AssemblyObject::swapJCS(App::DocumentObject* joint) { auto propElement1 = dynamic_cast(joint->getPropertyByName("Element1")); auto propElement2 = dynamic_cast(joint->getPropertyByName("Element2")); if (propElement1 && propElement2) { auto temp = std::string(propElement1->getValue()); propElement1->setValue(propElement2->getValue()); propElement2->setValue(temp); } auto propVertex1 = dynamic_cast(joint->getPropertyByName("Vertex1")); auto propVertex2 = dynamic_cast(joint->getPropertyByName("Vertex2")); if (propVertex1 && propVertex2) { auto temp = std::string(propVertex1->getValue()); propVertex1->setValue(propVertex2->getValue()); propVertex2->setValue(temp); } auto propPlacement1 = dynamic_cast(joint->getPropertyByName("Placement1")); auto propPlacement2 = dynamic_cast(joint->getPropertyByName("Placement2")); if (propPlacement1 && propPlacement2) { auto temp = propPlacement1->getValue(); propPlacement1->setValue(propPlacement2->getValue()); propPlacement2->setValue(temp); } auto propObject1 = dynamic_cast(joint->getPropertyByName("Object1")); auto propObject2 = dynamic_cast(joint->getPropertyByName("Object2")); if (propObject1 && propObject2) { auto temp = std::string(propObject1->getValue()); propObject1->setValue(propObject2->getValue()); propObject2->setValue(temp); } auto propPart1 = dynamic_cast(joint->getPropertyByName("Part1")); auto propPart2 = dynamic_cast(joint->getPropertyByName("Part2")); if (propPart1 && propPart2) { auto temp = propPart1->getValue(); propPart1->setValue(propPart2->getValue()); propPart2->setValue(temp); } } bool AssemblyObject::isEdgeType(App::DocumentObject* obj, const char* elName, GeomAbs_CurveType type) { PartApp::Feature* base = static_cast(obj); const PartApp::TopoShape& TopShape = base->Shape.getShape(); // Check for valid face types TopoDS_Edge edge = TopoDS::Edge(TopShape.getSubShape(elName)); BRepAdaptor_Curve sf(edge); if (sf.GetType() == type) { return true; } return false; } bool AssemblyObject::isFaceType(App::DocumentObject* obj, const char* elName, GeomAbs_SurfaceType type) { auto base = static_cast(obj); PartApp::TopoShape TopShape = base->Shape.getShape(); // Check for valid face types TopoDS_Face face = TopoDS::Face(TopShape.getSubShape(elName)); BRepAdaptor_Surface sf(face); // GeomAbs_Plane GeomAbs_Cylinder GeomAbs_Cone GeomAbs_Sphere GeomAbs_Thorus if (sf.GetType() == type) { return true; } return false; } double AssemblyObject::getFaceRadius(App::DocumentObject* obj, const char* elt) { auto base = static_cast(obj); const PartApp::TopoShape& TopShape = base->Shape.getShape(); // Check for valid face types TopoDS_Face face = TopoDS::Face(TopShape.getSubShape(elt)); BRepAdaptor_Surface sf(face); if (sf.GetType() == GeomAbs_Cylinder) { return sf.Cylinder().Radius(); } else if (sf.GetType() == GeomAbs_Sphere) { return sf.Sphere().Radius(); } return 0.0; } double AssemblyObject::getEdgeRadius(App::DocumentObject* obj, const char* elt) { auto base = static_cast(obj); const PartApp::TopoShape& TopShape = base->Shape.getShape(); // Check for valid face types TopoDS_Edge edge = TopoDS::Edge(TopShape.getSubShape(elt)); BRepAdaptor_Curve sf(edge); if (sf.GetType() == GeomAbs_Circle) { return sf.Circle().Radius(); } return 0.0; } void printPlacement(Base::Placement plc, const char* name) { Base::Vector3d pos = plc.getPosition(); Base::Vector3d axis; double angle; Base::Rotation rot = plc.getRotation(); rot.getRawValue(axis, angle); Base::Console().Warning( "placement %s : position (%.1f, %.1f, %.1f) - axis (%.1f, %.1f, %.1f) angle %.1f\n", name, pos.x, pos.y, pos.z, axis.x, axis.y, axis.z, angle); } void AssemblyObject::setJointActivated(App::DocumentObject* joint, bool val) { auto* propActivated = dynamic_cast(joint->getPropertyByName("Activated")); if (propActivated) { propActivated->setValue(val); } } bool AssemblyObject::getJointActivated(App::DocumentObject* joint) { auto* propActivated = dynamic_cast(joint->getPropertyByName("Activated")); if (propActivated) { return propActivated->getValue(); } return false; } Base::Placement AssemblyObject::getPlacementFromProp(App::DocumentObject* obj, const char* propName) { Base::Placement plc = Base::Placement(); auto* propPlacement = dynamic_cast(obj->getPropertyByName(propName)); if (propPlacement) { plc = propPlacement->getValue(); } return plc; } bool AssemblyObject::getTargetPlacementRelativeTo(Base::Placement& foundPlc, App::DocumentObject* targetObj, App::DocumentObject* part, App::DocumentObject* container, bool inContainerBranch, bool ignorePlacement) { inContainerBranch = inContainerBranch || (!ignorePlacement && part == container); if (targetObj == part && inContainerBranch && !ignorePlacement) { foundPlc = getPlacementFromProp(targetObj, "Placement"); return true; } if (part->isDerivedFrom(App::DocumentObjectGroup::getClassTypeId())) { for (auto& obj : part->getOutList()) { bool found = getTargetPlacementRelativeTo(foundPlc, targetObj, obj, container, inContainerBranch, ignorePlacement); if (found) { return true; } } } else if (part->isDerivedFrom(Assembly::AssemblyObject::getClassTypeId()) || part->isDerivedFrom(App::Part::getClassTypeId()) || part->isDerivedFrom(PartDesign::Body::getClassTypeId())) { for (auto& obj : part->getOutList()) { bool found = getTargetPlacementRelativeTo(foundPlc, targetObj, obj, container, inContainerBranch); if (!found) { continue; } if (!ignorePlacement) { foundPlc = getPlacementFromProp(part, "Placement") * foundPlc; } return true; } } else if (auto link = dynamic_cast(part)) { auto linked_obj = link->getLinkedObject(); if (dynamic_cast(linked_obj) || dynamic_cast(linked_obj)) { for (auto& obj : linked_obj->getOutList()) { bool found = getTargetPlacementRelativeTo(foundPlc, targetObj, obj, container, inContainerBranch); if (!found) { continue; } foundPlc = getPlacementFromProp(link, "Placement") * foundPlc; return true; } } bool found = getTargetPlacementRelativeTo(foundPlc, targetObj, linked_obj, container, inContainerBranch, true); if (found) { if (!ignorePlacement) { foundPlc = getPlacementFromProp(link, "Placement") * foundPlc; } return true; } } return false; } Base::Placement AssemblyObject::getGlobalPlacement(App::DocumentObject* targetObj, App::DocumentObject* container) { bool inContainerBranch = (container == nullptr); auto rootObjects = App::GetApplication().getActiveDocument()->getRootObjects(); for (auto& part : rootObjects) { Base::Placement foundPlc; bool found = getTargetPlacementRelativeTo(foundPlc, targetObj, part, container, inContainerBranch); if (found) { return foundPlc; } } return Base::Placement(); } Base::Placement AssemblyObject::getGlobalPlacement(App::DocumentObject* joint, const char* targetObj, const char* container) { App::DocumentObject* obj = getObjFromNameProp(joint, targetObj, container); App::DocumentObject* part = getLinkObjFromProp(joint, container); return getGlobalPlacement(obj, part); } double AssemblyObject::getJointDistance(App::DocumentObject* joint) { double distance = 0.0; auto* prop = dynamic_cast(joint->getPropertyByName("Distance")); if (prop) { distance = prop->getValue(); } return distance; } JointType AssemblyObject::getJointType(App::DocumentObject* joint) { JointType jointType = JointType::Fixed; auto* prop = dynamic_cast(joint->getPropertyByName("JointType")); if (prop) { jointType = static_cast(prop->getValue()); } return jointType; } const char* AssemblyObject::getElementFromProp(App::DocumentObject* obj, const char* propName) { auto* prop = dynamic_cast(obj->getPropertyByName(propName)); if (!prop) { return ""; } return prop->getValue(); } std::string AssemblyObject::getElementTypeFromProp(App::DocumentObject* obj, const char* propName) { // The prop is going to be something like 'Edge14' or 'Face7'. We need 'Edge' or 'Face' std::string elementType; for (char ch : std::string(getElementFromProp(obj, propName))) { if (std::isalpha(ch)) { elementType += ch; } } return elementType; } App::DocumentObject* AssemblyObject::getLinkObjFromProp(App::DocumentObject* joint, const char* propLinkName) { auto* propObj = dynamic_cast(joint->getPropertyByName(propLinkName)); if (!propObj) { return nullptr; } return propObj->getValue(); } App::DocumentObject* AssemblyObject::getObjFromNameProp(App::DocumentObject* joint, const char* pObjName, const char* pPart) { auto* propObjName = dynamic_cast(joint->getPropertyByName(pObjName)); if (!propObjName) { return nullptr; } std::string objName = std::string(propObjName->getValue()); App::DocumentObject* containingPart = getLinkObjFromProp(joint, pPart); if (!containingPart) { return nullptr; } if (objName == containingPart->getNameInDocument()) { return containingPart; } /*if (containingPart->getTypeId().isDerivedFrom(App::Link::getClassTypeId())) { App::Link* link = dynamic_cast(containingPart); containingPart = link->getLinkedObject(); if (!containingPart) { return nullptr; } }*/ for (auto obj : containingPart->getOutListRecursive()) { if (objName == obj->getNameInDocument()) { return obj; } } return nullptr; } App::DocumentObject* AssemblyObject::getLinkedObjFromNameProp(App::DocumentObject* joint, const char* pObjName, const char* pPart) { auto* obj = getObjFromNameProp(joint, pObjName, pPart); if (obj) { return obj->getLinkedObject(true); } return nullptr; } /*void Part::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) { App::Part::handleChangedPropertyType(reader, TypeName, prop); }*/ /* Apparently not necessary as App::Part doesn't have this. // Python Assembly feature --------------------------------------------------------- namespace App { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(Assembly::AssemblyObjectPython, Assembly::AssemblyObject) template<> const char* Assembly::AssemblyObjectPython::getViewProviderName() const { return "AssemblyGui::ViewProviderAssembly"; } template<> PyObject* Assembly::AssemblyObjectPython::getPyObject() { if (PythonObject.is(Py::_None())) { // ref counter is set to 1 PythonObject = Py::Object(new FeaturePythonPyT(this), true); } return Py::new_reference_to(PythonObject); } /// @endcond // explicit template instantiation template class AssemblyExport FeaturePythonT; }// namespace App*/