diff --git a/src/Gui/View3DInventorViewer.cpp b/src/Gui/View3DInventorViewer.cpp index 065327606d..110ddf64a7 100644 --- a/src/Gui/View3DInventorViewer.cpp +++ b/src/Gui/View3DInventorViewer.cpp @@ -2737,7 +2737,7 @@ SbVec3f View3DInventorViewer::getPointOnLine(const SbVec2s& pnt, const SbVec3f& SbLine projectedLine = projectLineOntoPlane(axisCenter, axisCenter + axis, focalPlane); ptOnFocalPlaneAndOnLine = projectedLine.getClosestPoint(ptOnFocalPlane); - // now we need the intersection point between + // now we need the intersection point between // - the line passing by ptOnFocalPlaneAndOnLine normal to focalPlane // - The line (axisCenter, axisCenter + axis) @@ -4077,4 +4077,4 @@ void View3DInventorViewer::dragLeaveEvent(QDragLeaveEvent* ev) inherited::dragLeaveEvent(ev); } -#include "moc_View3DInventorViewer.cpp" +#include "moc_View3DInventorViewer.cpp" // NOLINT diff --git a/src/Mod/Assembly/App/AssemblyObject.cpp b/src/Mod/Assembly/App/AssemblyObject.cpp index 624196f3af..fab9d80184 100644 --- a/src/Mod/Assembly/App/AssemblyObject.cpp +++ b/src/Mod/Assembly/App/AssemblyObject.cpp @@ -100,36 +100,257 @@ PyObject* AssemblyObject::getPyObject() return Py::new_reference_to(PythonObject); } -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); - } +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; } - return jointsOf; + 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; } -std::vector AssemblyObject::getJointsOfPart(App::DocumentObject* part) +void AssemblyObject::preDrag(std::vector dragParts) { - std::vector joints = getJoints(false); - std::vector jointsOf; + solve(); - 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); - } + dragMbdParts.clear(); + for (auto part : dragParts) { + dragMbdParts.push_back(getMbDPart(part)); } - return jointsOf; + 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, @@ -160,46 +381,21 @@ App::DocumentObject* AssemblyObject::getJointOfPartConnectingToGround(App::Docum return nullptr; } -bool AssemblyObject::isJointConnectingPartToGround(App::DocumentObject* joint, const char* propname) +JointGroup* AssemblyObject::getJointGroup() { + App::Document* doc = getDocument(); - auto* propPart = dynamic_cast(joint->getPropertyByName(propname)); - if (!propPart) { - return false; + std::vector jointGroups = + doc->getObjectsOfType(Assembly::JointGroup::getClassTypeId()); + if (jointGroups.empty()) { + return nullptr; } - 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; + for (auto jointGroup : jointGroups) { + if (hasObject(jointGroup)) { + return dynamic_cast(jointGroup); } - - 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; + return nullptr; } std::vector AssemblyObject::getJoints(bool updateJCS) @@ -263,6 +459,151 @@ std::vector AssemblyObject::getGroundedJoints() 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) { @@ -369,205 +710,6 @@ bool AssemblyObject::isPartConnected(App::DocumentObject* obj) return false; } -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); -} - -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::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); -} - void AssemblyObject::jointParts(std::vector joints) { for (auto* joint : joints) { @@ -582,55 +724,6 @@ void AssemblyObject::jointParts(std::vector joints) } } -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::exportAsASMT(std::string fileName) -{ - mbdAssembly = makeMbdAssembly(); - objectPartMap.clear(); - fixGroundedParts(); - - std::vector joints = getJoints(); - - jointParts(joints); - - mbdAssembly->outputFile(fileName); -} - std::shared_ptr AssemblyObject::makeMbdJointOfType(App::DocumentObject* joint, JointType type) { @@ -1029,17 +1122,17 @@ std::shared_ptr AssemblyObject::getMbDPart(App::DocumentObject* obj) std::shared_ptr AssemblyObject::makeMbdPart(std::string& name, Base::Placement plc, double mass) { - auto mdbPart = CREATE::With(); - mdbPart->setName(name); + 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); - mdbPart->setPrincipalMassMarker(massMarker); + mbdPart->setPrincipalMassMarker(massMarker); Base::Vector3d pos = plc.getPosition(); - mdbPart->setPosition3D(pos.x, pos.y, pos.z); + 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 @@ -1049,20 +1142,12 @@ AssemblyObject::makeMbdPart(std::string& name, Base::Placement plc, double mass) Base::Vector3d r0 = mat.getRow(0); Base::Vector3d r1 = mat.getRow(1); Base::Vector3d r2 = mat.getRow(2); - mdbPart->setRotationMatrix(r0.x, r0.y, r0.z, r1.x, r1.y, r1.z, r2.x, r2.y, r2.z); + 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); - mdbPart->setQuarternions(q0, q1, q2, q3);*/ + mbdPart->setQuarternions(q0, q1, q2, q3);*/ - return mdbPart; -} - -std::shared_ptr AssemblyObject::makeMbdAssembly() -{ - auto assembly = CREATE::With(); - assembly->setName("OndselAssembly"); - - return assembly; + return mbdPart; } std::shared_ptr AssemblyObject::makeMbdMarker(std::string& name, Base::Placement& plc) @@ -1087,6 +1172,134 @@ std::shared_ptr AssemblyObject::makeMbdMarker(std::string& name, Bas 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")); @@ -1128,164 +1341,22 @@ void AssemblyObject::swapJCS(App::DocumentObject* joint) } } -void AssemblyObject::savePlacementsForUndo() +bool AssemblyObject::isEdgeType(App::DocumentObject* obj, + const char* elName, + GeomAbs_CurveType type) { - previousPositions.clear(); + PartApp::Feature* base = static_cast(obj); + const PartApp::TopoShape& TopShape = base->Shape.getShape(); - for (auto& pair : objectPartMap) { - App::DocumentObject* obj = pair.first; - if (!obj) { - continue; - } + // Check for valid face types + TopoDS_Edge edge = TopoDS::Edge(TopShape.getSubShape(elName)); + BRepAdaptor_Curve sf(edge); - 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; + if (sf.GetType() == type) { + return true; } - 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::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); - } - } -} - -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; + return false; } bool AssemblyObject::isFaceType(App::DocumentObject* obj, @@ -1306,24 +1377,6 @@ bool AssemblyObject::isFaceType(App::DocumentObject* obj, return false; } -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; -} - double AssemblyObject::getFaceRadius(App::DocumentObject* obj, const char* elt) { auto base = static_cast(obj); @@ -1359,9 +1412,6 @@ double AssemblyObject::getEdgeRadius(App::DocumentObject* obj, const char* elt) return 0.0; } - -// ======================================= Utils ====================================== - void printPlacement(Base::Placement plc, const char* name) { Base::Vector3d pos = plc.getPosition(); diff --git a/src/Mod/Assembly/App/AssemblyObject.h b/src/Mod/Assembly/App/AssemblyObject.h index 7bd7a51b78..0bf11a3fe6 100644 --- a/src/Mod/Assembly/App/AssemblyObject.h +++ b/src/Mod/Assembly/App/AssemblyObject.h @@ -50,6 +50,7 @@ class Placement; class Rotation; } // namespace Base + namespace Assembly { @@ -82,11 +83,25 @@ public: return "AssemblyGui::ViewProviderAssembly"; } + /* Solve the assembly. It will update first the joints, solve, update placements of the parts + and redraw the joints Args : enableRedo : This store initial positions to enable undo while + being in an active transaction (joint creation).*/ int solve(bool enableRedo = false); + void preDrag(std::vector dragParts); + void doDragStep(); + void postDrag(); void savePlacementsForUndo(); void undoSolve(); void clearUndo(); + void exportAsASMT(std::string fileName); + + void setNewPlacements(); + void recomputeJointPlacements(std::vector joints); + void redrawJointPlacements(std::vector joints); + + + // Ondsel Solver interface std::shared_ptr makeMbdAssembly(); std::shared_ptr makeMbdPart(std::string& name, Base::Placement plc = Base::Placement(), double mass = 1.0); @@ -101,22 +116,24 @@ public: std::shared_ptr makeMbdJointDistanceFaceEdge(App::DocumentObject* joint); std::shared_ptr makeMbdJointDistanceEdgeEdge(App::DocumentObject* joint); std::shared_ptr makeMbdJointDistanceFaceFace(App::DocumentObject* joint); - std::string handleOneSideOfJoint(App::DocumentObject* joint, const char* propObjLinkName, const char* propPartName, const char* propPlcName); + void jointParts(std::vector joints); + JointGroup* getJointGroup(); std::vector getJoints(bool updateJCS = true); + std::vector getGroundedJoints(); std::vector getJointsOfObj(App::DocumentObject* obj); std::vector getJointsOfPart(App::DocumentObject* part); App::DocumentObject* getJointOfPartConnectingToGround(App::DocumentObject* part, std::string& name); - bool isJointConnectingPartToGround(App::DocumentObject* joint, const char* partPropName); - std::vector getGroundedJoints(); - void fixGroundedPart(App::DocumentObject* obj, Base::Placement& plc, std::string& jointName); - std::vector fixGroundedParts(); std::vector getGroundedParts(); + std::vector fixGroundedParts(); + void fixGroundedPart(App::DocumentObject* obj, Base::Placement& plc, std::string& jointName); + + bool isJointConnectingPartToGround(App::DocumentObject* joint, const char* partPropName); void removeUnconnectedJoints(std::vector& joints, std::vector groundedObjs); @@ -125,17 +142,9 @@ public: const std::vector& joints); std::vector getConnectedParts(App::DocumentObject* part, const std::vector& joints); - - JointGroup* getJointGroup(); - - void swapJCS(App::DocumentObject* joint); - - void setNewPlacements(); - void redrawJointPlacements(std::vector joints); - void recomputeJointPlacements(std::vector joints); - bool isPartGrounded(App::DocumentObject* part); bool isPartConnected(App::DocumentObject* part); + std::vector getDownstreamParts(App::DocumentObject* part, App::DocumentObject* joint); std::vector getUpstreamParts(App::DocumentObject* part, int limit = 0); @@ -144,17 +153,13 @@ public: double getObjMass(App::DocumentObject* obj); void setObjMasses(std::vector> objectMasses); - bool isEdgeType(App::DocumentObject* obj, const char* elName, GeomAbs_CurveType type); - bool isFaceType(App::DocumentObject* obj, const char* elName, GeomAbs_SurfaceType type); - double getFaceRadius(App::DocumentObject* obj, const char* elName); - double getEdgeRadius(App::DocumentObject* obj, const char* elName); - private: std::shared_ptr mbdAssembly; std::unordered_map> objectPartMap; std::vector> objMasses; + std::vector> dragMbdParts; std::vector> previousPositions; @@ -166,6 +171,13 @@ public: // Can't put the functions by themselves in AssemblyUtils.cpp : // see https://forum.freecad.org/viewtopic.php?p=729577#p729577 + void swapJCS(App::DocumentObject* joint); + + bool isEdgeType(App::DocumentObject* obj, const char* elName, GeomAbs_CurveType type); + bool isFaceType(App::DocumentObject* obj, const char* elName, GeomAbs_SurfaceType type); + double getFaceRadius(App::DocumentObject* obj, const char* elName); + double getEdgeRadius(App::DocumentObject* obj, const char* elName); + // getters to get from properties static void setJointActivated(App::DocumentObject* joint, bool val); static bool getJointActivated(App::DocumentObject* joint); diff --git a/src/Mod/Assembly/CommandCreateJoint.py b/src/Mod/Assembly/CommandCreateJoint.py index 297f9392cf..1c6ea545c8 100644 --- a/src/Mod/Assembly/CommandCreateJoint.py +++ b/src/Mod/Assembly/CommandCreateJoint.py @@ -64,13 +64,19 @@ class CommandCreateJointFixed: "Pixmap": "Assembly_CreateJointFixed", "MenuText": QT_TRANSLATE_NOOP( "Assembly_CreateJointFixed", - "If an assembly is active : Create a Fixed Joint.\n If a part is active : Position sub parts by matching.", + "Create a Fixed Joint", ), "Accel": "J", "ToolTip": "

" + QT_TRANSLATE_NOOP( "Assembly_CreateJointFixed", - "Create a Fixed Joint: Permanently locks two parts together, preventing any movement or rotation.", + "1 - If an assembly is active : Create a joint permanently locking two parts together, preventing any movement or rotation.", + ) + + "

" + + "

" + + QT_TRANSLATE_NOOP( + "Assembly_CreateJointFixed", + "2 - If a part is active : Position sub parts by matching seleted coordinate systems. The second part selected will move.", ) + "

", "CmdType": "ForEdit", @@ -270,6 +276,7 @@ class CommandToggleGrounded: part_containing_obj = UtilsAssembly.getContainingPart(full_element_name, obj) # Check if part is grounded and if so delete the joint. + ungrounded = False for joint in joint_group.Group: if ( hasattr(joint, "ObjectToGround") @@ -281,10 +288,12 @@ class CommandToggleGrounded: doc = App.ActiveDocument doc.removeObject(joint.Name) doc.recompute() - return + ungrounded = True + break + if ungrounded: + continue # Create groundedJoint. - part_containing_obj.Label = part_containing_obj.Label + " 🔒" ground = joint_group.newObject("App::FeaturePython", "GroundedJoint") JointObject.GroundedJoint(ground, part_containing_obj) diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp index 8c70690d04..ab7a4a1c46 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.cpp @@ -75,6 +75,7 @@ PROPERTY_SOURCE(AssemblyGui::ViewProviderAssembly, Gui::ViewProviderPart) ViewProviderAssembly::ViewProviderAssembly() : SelectionObserver(true) + , dragMode(DragMode::None) , canStartDragging(false) , partMoving(false) , enableMovement(true) @@ -116,7 +117,7 @@ bool ViewProviderAssembly::doubleClicked() bool ViewProviderAssembly::canDragObject(App::DocumentObject* obj) const { - Base::Console().Warning("ViewProviderAssembly::canDragObject\n"); + // The user should not be able to drag the joint group out of the assembly if (!obj || obj->getTypeId() == Assembly::JointGroup::getClassTypeId()) { return false; } @@ -210,7 +211,7 @@ void ViewProviderAssembly::unsetEdit(int ModNum) PARTKEY); } -bool ViewProviderAssembly::isInEditMode() +bool ViewProviderAssembly::isInEditMode() const { App::DocumentObject* activePart = getActivePart(); if (!activePart) { @@ -220,20 +221,18 @@ bool ViewProviderAssembly::isInEditMode() return activePart == this->getObject(); } -App::DocumentObject* ViewProviderAssembly::getActivePart() +App::DocumentObject* ViewProviderAssembly::getActivePart() const { - App::DocumentObject* activePart = nullptr; auto activeDoc = Gui::Application::Instance->activeDocument(); if (!activeDoc) { activeDoc = getDocument(); } - auto activeView = activeDoc->setActiveView(this); + auto activeView = activeDoc->getActiveView(); if (!activeView) { return nullptr; } - activePart = activeView->getActiveObject(PARTKEY); - return activePart; + return activeView->getActiveObject(PARTKEY); } bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInventorViewer* viewer) @@ -243,21 +242,21 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent canStartDragging = false; if (enableMovement && getSelectedObjectsWithinAssembly()) { - moveMode = findMoveMode(); + dragMode = findDragMode(); - if (moveMode == MoveMode::None) { + if (dragMode == DragMode::None) { return false; } SbVec3f vec; - if (moveMode == MoveMode::RotationOnPlane - || moveMode == MoveMode::TranslationOnAxisAndRotationOnePlane) { + if (dragMode == DragMode::RotationOnPlane + || dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) { vec = viewer->getPointOnXYPlaneOfPlacement(cursorPos, jcsGlobalPlc); initialPositionRot = Base::Vector3d(vec[0], vec[1], vec[2]); } - if (moveMode == MoveMode::TranslationOnAxis - || moveMode == MoveMode::TranslationOnAxisAndRotationOnePlane) { + if (dragMode == DragMode::TranslationOnAxis + || dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) { Base::Vector3d zAxis = jcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., 1.)); Base::Vector3d pos = jcsGlobalPlc.getPosition(); @@ -266,9 +265,10 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent vec = viewer->getPointOnLine(cursorPos, axisCenter, axis); initialPosition = Base::Vector3d(vec[0], vec[1], vec[2]); } - else if (moveMode != MoveMode::RotationOnPlane) { + else if (dragMode != DragMode::RotationOnPlane) { vec = viewer->getPointOnFocalPlane(cursorPos); initialPosition = Base::Vector3d(vec[0], vec[1], vec[2]); + prevPosition = initialPosition; } initMove(); @@ -278,14 +278,14 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent // Do the dragging of parts if (partMoving) { Base::Vector3d newPos, newPosRot; - if (moveMode == MoveMode::RotationOnPlane - || moveMode == MoveMode::TranslationOnAxisAndRotationOnePlane) { + if (dragMode == DragMode::RotationOnPlane + || dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) { SbVec3f vec = viewer->getPointOnXYPlaneOfPlacement(cursorPos, jcsGlobalPlc); newPosRot = Base::Vector3d(vec[0], vec[1], vec[2]); } - if (moveMode == MoveMode::TranslationOnAxis - || moveMode == MoveMode::TranslationOnAxisAndRotationOnePlane) { + if (dragMode == DragMode::TranslationOnAxis + || dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) { Base::Vector3d zAxis = jcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., 1.)); Base::Vector3d pos = jcsGlobalPlc.getPosition(); SbVec3f axisCenter(pos.x, pos.y, pos.z); @@ -293,7 +293,7 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent SbVec3f vec = viewer->getPointOnLine(cursorPos, axisCenter, axis); newPos = Base::Vector3d(vec[0], vec[1], vec[2]); } - else if (moveMode != MoveMode::RotationOnPlane) { + else if (dragMode != DragMode::RotationOnPlane) { SbVec3f vec = viewer->getPointOnFocalPlane(cursorPos); newPos = Base::Vector3d(vec[0], vec[1], vec[2]); } @@ -307,7 +307,7 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent Base::Placement plc = pair.second; // Base::Console().Warning("newPos %f %f %f\n", newPos.x, newPos.y, newPos.z); - if (moveMode == MoveMode::RotationOnPlane) { + if (dragMode == DragMode::RotationOnPlane) { Base::Vector3d center = jcsGlobalPlc.getPosition(); Base::Vector3d norm = jcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., -1.)); @@ -320,13 +320,18 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent Base::Placement jcsPlcRelativeToPart = plc.inverse() * jcsGlobalPlc; plc = rotatedGlovalJcsPlc * jcsPlcRelativeToPart.inverse(); } - else if (moveMode == MoveMode::TranslationOnAxisAndRotationOnePlane) { + else if (dragMode == DragMode::TranslationOnAxis) { + Base::Vector3d pos = plc.getPosition() + (newPos - initialPosition); + plc.setPosition(pos); + } + else if (dragMode == DragMode::TranslationOnAxisAndRotationOnePlane) { Base::Vector3d pos = plc.getPosition() + (newPos - initialPosition); plc.setPosition(pos); Base::Placement newJcsGlobalPlc = jcsGlobalPlc; newJcsGlobalPlc.setPosition(jcsGlobalPlc.getPosition() + (newPos - initialPosition)); + Base::Vector3d center = newJcsGlobalPlc.getPosition(); Base::Vector3d norm = newJcsGlobalPlc.getRotation().multVec(Base::Vector3d(0., 0., -1.)); @@ -342,8 +347,12 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent Base::Placement jcsPlcRelativeToPart = plc.inverse() * newJcsGlobalPlc; plc = rotatedGlovalJcsPlc * jcsPlcRelativeToPart.inverse(); } - else { - Base::Vector3d pos = newPos + (plc.getPosition() - initialPosition); + else { // DragMode::Translation + Base::Vector3d delta = newPos - prevPosition; + prevPosition = newPos; + + Base::Vector3d pos = propPlacement->getValue().getPosition() + delta; + // Base::Vector3d pos = newPos + (plc.getPosition() - initialPosition); plc.setPosition(pos); } propPlacement->setValue(plc); @@ -356,6 +365,7 @@ bool ViewProviderAssembly::mouseMove(const SbVec2s& cursorPos, Gui::View3DInvent if (solveOnMove) { auto* assemblyPart = static_cast(getObject()); assemblyPart->solve(); + // assemblyPart->doDragStep(); } } return false; @@ -532,7 +542,7 @@ App::DocumentObject* ViewProviderAssembly::getObjectFromSubNames(std::vectorgetObject(objName.c_str()); } -ViewProviderAssembly::MoveMode ViewProviderAssembly::findMoveMode() +ViewProviderAssembly::DragMode ViewProviderAssembly::findDragMode() { if (docsToMove.size() == 1) { auto* assemblyPart = static_cast(getObject()); @@ -541,7 +551,7 @@ ViewProviderAssembly::MoveMode ViewProviderAssembly::findMoveMode() assemblyPart->getJointOfPartConnectingToGround(docsToMove[0].first, partPropName); if (!movingJoint) { - return MoveMode::Translation; + return DragMode::Translation; } JointType jointType = AssemblyObject::getJointType(movingJoint); @@ -553,7 +563,7 @@ ViewProviderAssembly::MoveMode ViewProviderAssembly::findMoveMode() assemblyPart->getUpstreamMovingPart(docsToMove[0].first); docsToMove.clear(); if (!upstreamPart) { - return MoveMode::None; + return DragMode::None; } auto* propPlacement = @@ -565,7 +575,7 @@ ViewProviderAssembly::MoveMode ViewProviderAssembly::findMoveMode() movingJoint = assemblyPart->getJointOfPartConnectingToGround(docsToMove[0].first, partPropName); if (!movingJoint) { - return MoveMode::Translation; + return DragMode::Translation; } jointType = AssemblyObject::getJointType(movingJoint); } @@ -597,20 +607,23 @@ ViewProviderAssembly::MoveMode ViewProviderAssembly::findMoveMode() } if (jointType == JointType::Revolute) { - return MoveMode::RotationOnPlane; + return DragMode::RotationOnPlane; } else if (jointType == JointType::Slider) { - return MoveMode::TranslationOnAxis; + return DragMode::TranslationOnAxis; } else if (jointType == JointType::Cylindrical) { - return MoveMode::TranslationOnAxisAndRotationOnePlane; + return DragMode::TranslationOnAxisAndRotationOnePlane; + } + else if (jointType == JointType::Ball) { + // return DragMode::Ball; } else if (jointType == JointType::Distance) { // depends on the type of distance. For example plane-plane: - // return MoveMode::TranslationOnPlane; + // return DragMode::TranslationOnPlane; } } - return MoveMode::Translation; + return DragMode::Translation; } void ViewProviderAssembly::initMove() @@ -627,14 +640,23 @@ void ViewProviderAssembly::initMove() viewerNotConst->setSelectionEnabled(false); } - objectMasses.clear(); + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Assembly"); + bool solveOnMove = hGrp->GetBool("SolveOnMove", true); + if (solveOnMove) { + objectMasses.clear(); + for (auto& pair : docsToMove) { + objectMasses.push_back({pair.first, 10.0}); + } - for (auto& pair : docsToMove) { - objectMasses.push_back({pair.first, 10.0}); + auto* assemblyPart = static_cast(getObject()); + assemblyPart->setObjMasses(objectMasses); + /*std::vector dragParts; + for (auto& pair : docsToMove) { + dragParts.push_back(pair.first); + } + assemblyPart->preDrag(dragParts);*/ } - - auto* assemblyPart = static_cast(getObject()); - assemblyPart->setObjMasses(objectMasses); } void ViewProviderAssembly::endMove() @@ -658,13 +680,18 @@ void ViewProviderAssembly::endMove() viewerNotConst->setSelectionEnabled(true); } - auto* assemblyPart = static_cast(getObject()); - assemblyPart->setObjMasses({}); + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( + "User parameter:BaseApp/Preferences/Mod/Assembly"); + bool solveOnMove = hGrp->GetBool("SolveOnMove", true); + if (solveOnMove) { + auto* assemblyPart = static_cast(getObject()); + // assemblyPart->postDrag(); + assemblyPart->setObjMasses({}); + } Gui::Command::commitCommand(); } - void ViewProviderAssembly::onSelectionChanged(const Gui::SelectionChanges& msg) { if (msg.Type == Gui::SelectionChanges::AddSelection diff --git a/src/Mod/Assembly/Gui/ViewProviderAssembly.h b/src/Mod/Assembly/Gui/ViewProviderAssembly.h index 9f57192e19..dc2dc8a4ff 100644 --- a/src/Mod/Assembly/Gui/ViewProviderAssembly.h +++ b/src/Mod/Assembly/Gui/ViewProviderAssembly.h @@ -45,6 +45,18 @@ class AssemblyGuiExport ViewProviderAssembly: public Gui::ViewProviderPart, Q_DECLARE_TR_FUNCTIONS(AssemblyGui::ViewProviderAssembly) PROPERTY_HEADER_WITH_OVERRIDE(AssemblyGui::ViewProviderAssembly); + enum class DragMode + { + Translation, + TranslationOnAxis, + TranslationOnPlane, + Rotation, + RotationOnPlane, + TranslationOnAxisAndRotationOnePlane, + Ball, + None, + }; + public: ViewProviderAssembly(); ~ViewProviderAssembly() override; @@ -59,7 +71,7 @@ public: //@{ bool setEdit(int ModNum) override; void unsetEdit(int ModNum) override; - bool isInEditMode(); + bool isInEditMode() const; /// Ask the view provider if it accepts object deletions while in edit bool acceptDeletionsInEdit() override @@ -69,19 +81,8 @@ public: bool canDragObject(App::DocumentObject*) const override; - App::DocumentObject* getActivePart(); + App::DocumentObject* getActivePart() const; - enum class MoveMode - { - Translation, - TranslationOnAxis, - TranslationOnPlane, - Rotation, - RotationOnPlane, - TranslationOnAxisAndRotationOnePlane, - None, - }; - MoveMode moveMode; /// is called when the provider is in edit and the mouse is moved bool mouseMove(const SbVec2s& pos, Gui::View3DInventorViewer* viewer) override; @@ -90,18 +91,11 @@ public: bool pressed, const SbVec2s& cursorPos, const Gui::View3DInventorViewer* viewer) override; - MoveMode findMoveMode(); + + /// Finds what drag mode should be used based on the user selection. + DragMode findDragMode(); void initMove(); void endMove(); - - - bool getSelectedObjectsWithinAssembly(); - App::DocumentObject* getObjectFromSubNames(std::vector& subNames); - std::vector parseSubNames(std::string& subNamesStr); - - /// Get the python wrapper for that ViewProvider - PyObject* getPyObject() override; - virtual void setEnableMovement(bool enable = true) { enableMovement = enable; @@ -111,17 +105,26 @@ public: return enableMovement; } + bool getSelectedObjectsWithinAssembly(); + App::DocumentObject* getObjectFromSubNames(std::vector& subNames); + std::vector parseSubNames(std::string& subNamesStr); + + /// Get the python wrapper for that ViewProvider + PyObject* getPyObject() override; + // protected: /// get called by the container whenever a property has been changed // void onChanged(const App::Property* prop) override; void onSelectionChanged(const Gui::SelectionChanges& msg) override; + DragMode dragMode; bool canStartDragging; bool partMoving; bool enableMovement; bool jointVisibilityBackup; int numberOfSel; + Base::Vector3d prevPosition; Base::Vector3d initialPosition; Base::Vector3d initialPositionRot; Base::Placement jcsPlc; diff --git a/src/Mod/Assembly/InitGui.py b/src/Mod/Assembly/InitGui.py index d8493837a3..b49cba344c 100644 --- a/src/Mod/Assembly/InitGui.py +++ b/src/Mod/Assembly/InitGui.py @@ -75,10 +75,13 @@ class AssemblyWorkbench(Workbench): FreeCADGui.addPreferencePage(PreferencesPage, QT_TRANSLATE_NOOP("QObject", "Assembly")) # build commands list - cmdlist = [ + cmdList = [ "Assembly_CreateAssembly", "Assembly_InsertLink", "Assembly_SolveAssembly", + ] + + cmdListMenuOnly = [ "Assembly_ExportASMT", ] @@ -93,12 +96,12 @@ class AssemblyWorkbench(Workbench): "Assembly_CreateJointDistance", ] - self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Assembly"), cmdlist) + self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Assembly"), cmdList) self.appendToolbar(QT_TRANSLATE_NOOP("Workbench", "Assembly Joints"), cmdListJoints) self.appendMenu( [QT_TRANSLATE_NOOP("Workbench", "&Assembly")], - cmdlist + ["Separator"] + cmdListJoints, + cmdList + cmdListMenuOnly + ["Separator"] + cmdListJoints, ) print("Assembly workbench loaded") diff --git a/src/Mod/Assembly/JointObject.py b/src/Mod/Assembly/JointObject.py index 8e445f7a47..f186a7da89 100644 --- a/src/Mod/Assembly/JointObject.py +++ b/src/Mod/Assembly/JointObject.py @@ -83,8 +83,6 @@ def solveIfAllowed(assembly, storePrev=False): class Joint: def __init__(self, joint, type_index): - self.Type = "Joint" - joint.Proxy = self joint.addProperty( @@ -237,12 +235,11 @@ class Joint: self.setJointConnectors(joint, []) - def __getstate__(self): - return self.Type + def dumps(self): + return None - def __setstate__(self, state): - if state: - self.Type = state + def loads(self, state): + return None def getAssembly(self, joint): return joint.InList[0] @@ -256,23 +253,25 @@ class Joint: # App.Console.PrintMessage("Change property: " + str(prop) + "\n") if prop == "Rotation" or prop == "Offset" or prop == "Distance": - if hasattr( - joint, "Vertex1" - ): # during loading the onchanged may be triggered before full init. + # during loading the onchanged may be triggered before full init. + if hasattr(joint, "Vertex1"): # so we check Vertex1 + self.updateJCSPlacements(joint) + obj1 = UtilsAssembly.getObjectInPart(joint.Object1, joint.Part1) + obj2 = UtilsAssembly.getObjectInPart(joint.Object2, joint.Part2) + presolved = self.preSolve( + joint, + obj1, + joint.Part1, + obj2, + joint.Part2, + False, + ) + isAssembly = self.getAssembly(joint).Type == "Assembly" - if isAssembly: + if isAssembly and not presolved: solveIfAllowed(self.getAssembly(joint)) else: self.updateJCSPlacements(joint) - obj1 = UtilsAssembly.getObjectInPart(joint.Object1, joint.Part1) - obj2 = UtilsAssembly.getObjectInPart(joint.Object2, joint.Part2) - self.preSolve( - joint, - obj1, - joint.Part1, - obj2, - joint.Part2, - ) def execute(self, fp): """Do something when doing a recomputation, this method is mandatory""" @@ -380,6 +379,8 @@ class Joint: elt_type, elt_index = UtilsAssembly.extract_type_and_number(elt) vtx_type, vtx_index = UtilsAssembly.extract_type_and_number(vtx) + isLine = False + if elt_type == "Vertex": vertex = obj.Shape.Vertexes[elt_index - 1] plc.Base = (vertex.X, vertex.Y, vertex.Z) @@ -406,6 +407,7 @@ class Joint: plc.Rotation = App.Rotation(curve.Rotation) if curve.TypeId == "Part::GeomLine": + isLine = True plane_normal = curve.Direction plane_origin = App.Vector(0, 0, 0) plane = Part.Plane(plane_origin, plane_normal) @@ -457,6 +459,15 @@ class Joint: # change plc to be relative to the object placement. plc = obj.Placement.inverse() * plc + # post-process of plc for some special cases + if elt_type == "Vertex": + plc.Rotation = App.Rotation() + elif isLine: + plane_normal = plc.Rotation.multVec(App.Vector(0, 0, 1)) + plane_origin = App.Vector(0, 0, 0) + plane = Part.Plane(plane_origin, plane_normal) + plc.Rotation = App.Rotation(plane.Rotation) + # change plc to be relative to the origin of the document. # global_plc = UtilsAssembly.getGlobalPlacement(obj, part) # plc = global_plc * plc @@ -535,7 +546,7 @@ class Joint: return App.Vector(res[0].X, res[0].Y, res[0].Z) return surface.Center - def preSolve(self, joint, obj1, part1, obj2, part2): + def preSolve(self, joint, obj1, part1, obj2, part2, savePlc=True): # The goal of this is to put the part in the correct position to avoid wrong placement by the solve. # we actually don't want to match perfectly the JCS, it is best to match them @@ -543,8 +554,9 @@ class Joint: sameDir = self.areJcsSameDir(joint) if hasattr(self, "part2Connected") and not self.part2Connected: - self.partMovedByPresolved = joint.Part2 - self.presolveBackupPlc = joint.Part2.Placement + if savePlc: + self.partMovedByPresolved = joint.Part2 + self.presolveBackupPlc = joint.Part2.Placement globalJcsPlc1 = UtilsAssembly.getJcsGlobalPlc( joint.Placement1, joint.Object1, joint.Part1 @@ -555,10 +567,12 @@ class Joint: if not sameDir: jcsPlc2 = self.flipPlacement(jcsPlc2) joint.Part2.Placement = globalJcsPlc1 * jcsPlc2.inverse() + return True elif hasattr(self, "part1Connected") and not self.part1Connected: - self.partMovedByPresolved = joint.Part1 - self.presolveBackupPlc = joint.Part1.Placement + if savePlc: + self.partMovedByPresolved = joint.Part1 + self.presolveBackupPlc = joint.Part1.Placement globalJcsPlc2 = UtilsAssembly.getJcsGlobalPlc( joint.Placement2, joint.Object2, joint.Part2 @@ -569,6 +583,8 @@ class Joint: if not sameDir: jcsPlc1 = self.flipPlacement(jcsPlc1) joint.Part1.Placement = globalJcsPlc2 * jcsPlc1.inverse() + return True + return False def undoPreSolve(self): if self.partMovedByPresolved: @@ -608,7 +624,10 @@ class ViewProviderJoint: camera = Gui.ActiveDocument.ActiveView.getCameraNode() self.cameraSensor = coin.SoFieldSensor(self.camera_callback, camera) - self.cameraSensor.attach(camera.height) + if isinstance(camera, coin.SoPerspectiveCamera): + self.cameraSensor.attach(camera.focalDistance) + elif isinstance(camera, coin.SoOrthographicCamera): + self.cameraSensor.attach(camera.height) self.app_obj = vobj.Object @@ -711,7 +730,14 @@ class ViewProviderJoint: def get_JCS_size(self): camera = Gui.ActiveDocument.ActiveView.getCameraNode() - if not camera: + + # Check if the camera is a perspective camera + if isinstance(camera, coin.SoPerspectiveCamera): + return camera.focalDistance.getValue() / 20 + elif isinstance(camera, coin.SoOrthographicCamera): + return camera.height.getValue() / 20 + else: + # Default value if camera type is unknown return 10 return camera.height.getValue() / 20 @@ -736,7 +762,8 @@ class ViewProviderJoint: plc = joint.Placement1 self.switch_JCS1.whichChild = coin.SO_SWITCH_ALL - self.set_JCS_placement(self.transform1, plc, joint.Object1, joint.Part1) + if joint.Part1: + self.set_JCS_placement(self.transform1, plc, joint.Object1, joint.Part1) else: self.switch_JCS1.whichChild = coin.SO_SWITCH_NONE @@ -745,7 +772,8 @@ class ViewProviderJoint: plc = joint.Placement2 self.switch_JCS2.whichChild = coin.SO_SWITCH_ALL - self.set_JCS_placement(self.transform2, plc, joint.Object2, joint.Part2) + if joint.Part2: + self.set_JCS_placement(self.transform2, plc, joint.Object2, joint.Part2) else: self.switch_JCS2.whichChild = coin.SO_SWITCH_NONE @@ -827,7 +855,6 @@ class ViewProviderJoint: class GroundedJoint: def __init__(self, joint, obj_to_ground): - self.Type = "GoundedJoint" joint.Proxy = self self.joint = joint @@ -852,12 +879,11 @@ class GroundedJoint: joint.Placement = obj_to_ground.Placement - def __getstate__(self): - return self.Type + def dumps(self): + return None - def __setstate__(self, state): - if state: - self.Type = state + def loads(self, state): + return None def onChanged(self, fp, prop): """Do something when a property has changed""" diff --git a/src/Mod/Assembly/UtilsAssembly.py b/src/Mod/Assembly/UtilsAssembly.py index 4bdaa28b2e..7e2630160a 100644 --- a/src/Mod/Assembly/UtilsAssembly.py +++ b/src/Mod/Assembly/UtilsAssembly.py @@ -220,7 +220,7 @@ def getContainingPart(full_name, selected_object, activeAssemblyOrPart=None): if linked_obj.hasObject(selected_object, True): if not activeAssemblyOrPart: return obj - elif ( + elif (linked_obj.Document == activeAssemblyOrPart.Document) and ( linked_obj.hasObject(activeAssemblyOrPart, True) or linked_obj == activeAssemblyOrPart ): @@ -282,9 +282,14 @@ def getJcsGlobalPlc(jcsPlc, objName, part): # The container is used to support cases where the same object appears at several places # which happens when you have a link to a part. def getGlobalPlacement(targetObj, container=None): + if targetObj is None: + return App.Placement() + inContainerBranch = container is None - for part in App.activeDocument().RootObjects: - foundPlacement = getTargetPlacementRelativeTo(targetObj, part, container, inContainerBranch) + for rootObj in App.activeDocument().RootObjects: + foundPlacement = getTargetPlacementRelativeTo( + targetObj, rootObj, container, inContainerBranch + ) if foundPlacement is not None: return foundPlacement @@ -330,6 +335,8 @@ def getTargetPlacementRelativeTo( elif part.TypeId == "App::Link": linked_obj = part.getLinkedObject() + if part == linked_obj or linked_obj is None: + return None # upon loading this can happen for external links. if linked_obj.TypeId in {"App::Part", "Assembly::AssemblyObject", "PartDesign::Body"}: for obj in linked_obj.OutList: