FreeCAD / src /Mod /Assembly /App /AssemblyUtils.cpp
AbdulElahGwaith's picture
Upload folder using huggingface_hub
985c397 verified
// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2023 Ondsel <development@ondsel.com> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include <BRepAdaptor_Curve.hxx>
#include <BRepAdaptor_Surface.hxx>
#include <TopoDS.hxx>
#include <TopoDS_Face.hxx>
#include <gp_Circ.hxx>
#include <gp_Cylinder.hxx>
#include <gp_Sphere.hxx>
#include <App/Application.h>
#include <App/Datums.h>
#include <App/Document.h>
#include <App/DocumentObject.h>
#include <App/PropertyStandard.h>
#include <App/Link.h>
#include <Base/Placement.h>
#include <Base/Tools.h>
#include <Base/Interpreter.h>
#include <Mod/Part/App/DatumFeature.h>
#include <Mod/Part/App/PartFeature.h>
#include <Mod/PartDesign/App/Body.h>
#include "AssemblyUtils.h"
#include "AssemblyObject.h"
#include "AssemblyLink.h"
#include "JointGroup.h"
namespace PartApp = Part;
// ======================================= Utils ======================================
namespace Assembly
{
void swapJCS(const App::DocumentObject* joint)
{
if (!joint) {
return;
}
auto pPlc1 = joint->getPropertyByName<App::PropertyPlacement>("Placement1");
auto pPlc2 = joint->getPropertyByName<App::PropertyPlacement>("Placement2");
if (pPlc1 && pPlc2) {
const auto temp = pPlc1->getValue();
pPlc1->setValue(pPlc2->getValue());
pPlc2->setValue(temp);
}
auto pRef1 = joint->getPropertyByName<App::PropertyXLinkSub>("Reference1");
auto pRef2 = joint->getPropertyByName<App::PropertyXLinkSub>("Reference2");
if (pRef1 && pRef2) {
auto temp = pRef1->getValue();
auto subs1 = pRef1->getSubValues();
auto subs2 = pRef2->getSubValues();
pRef1->setValue(pRef2->getValue());
pRef1->setSubValues(std::move(subs2));
pRef2->setValue(temp);
pRef2->setSubValues(std::move(subs1));
}
}
bool isEdgeType(const App::DocumentObject* obj, const std::string& elName, const GeomAbs_CurveType type)
{
auto* base = dynamic_cast<const PartApp::Feature*>(obj);
if (!base) {
return false;
}
const auto& TopShape = base->Shape.getShape();
// Check for valid face types
const auto edge = TopoDS::Edge(TopShape.getSubShape(elName.c_str()));
BRepAdaptor_Curve sf(edge);
return sf.GetType() == type;
}
bool isFaceType(const App::DocumentObject* obj, const std::string& elName, const GeomAbs_SurfaceType type)
{
auto* base = dynamic_cast<const PartApp::Feature*>(obj);
if (!base) {
return false;
}
const auto TopShape = base->Shape.getShape();
// Check for valid face types
const auto face = TopoDS::Face(TopShape.getSubShape(elName.c_str()));
BRepAdaptor_Surface sf(face);
return sf.GetType() == type;
}
double getFaceRadius(const App::DocumentObject* obj, const std::string& elt)
{
auto* base = dynamic_cast<const PartApp::Feature*>(obj);
if (!base) {
return 0.0;
}
const PartApp::TopoShape& TopShape = base->Shape.getShape();
// Check for valid face types
TopoDS_Face face = TopoDS::Face(TopShape.getSubShape(elt.c_str()));
BRepAdaptor_Surface sf(face);
const auto type = sf.GetType();
return type == GeomAbs_Cylinder ? sf.Cylinder().Radius()
: type == GeomAbs_Sphere ? sf.Sphere().Radius()
: 0.0;
}
double getEdgeRadius(const App::DocumentObject* obj, const std::string& elt)
{
auto* base = dynamic_cast<const PartApp::Feature*>(obj);
if (!base) {
return 0.0;
}
const auto& TopShape = base->Shape.getShape();
// Check for valid face types
const auto edge = TopoDS::Edge(TopShape.getSubShape(elt.c_str()));
BRepAdaptor_Curve sf(edge);
return sf.GetType() == GeomAbs_Circle ? sf.Circle().Radius() : 0.0;
}
DistanceType getDistanceType(App::DocumentObject* joint)
{
if (!joint) {
return DistanceType::Other;
}
const auto type1 = getElementTypeFromProp(joint, "Reference1");
const auto type2 = getElementTypeFromProp(joint, "Reference2");
auto elt1 = getElementFromProp(joint, "Reference1");
auto elt2 = getElementFromProp(joint, "Reference2");
auto* obj1 = getLinkedObjFromRef(joint, "Reference1");
auto* obj2 = getLinkedObjFromRef(joint, "Reference2");
if (type1 == "Vertex" && type2 == "Vertex") {
return DistanceType::PointPoint;
}
else if (type1 == "Edge" && type2 == "Edge") {
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)) {
return DistanceType::LineLine;
}
else if (isEdgeType(obj2, elt2, GeomAbs_Circle)) {
return DistanceType::LineCircle;
}
// 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)) {
return DistanceType::CircleCircle;
}
// TODO : other cases Ellipse, parabola, hyperbola...
}
}
else if (type1 == "Face" && type2 == "Face") {
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)) {
return DistanceType::PlanePlane;
}
else if (isFaceType(obj2, elt2, GeomAbs_Cylinder)) {
return DistanceType::PlaneCylinder;
}
else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) {
return DistanceType::PlaneSphere;
}
else if (isFaceType(obj2, elt2, GeomAbs_Cone)) {
return DistanceType::PlaneCone;
}
else if (isFaceType(obj2, elt2, GeomAbs_Torus)) {
return DistanceType::PlaneTorus;
}
}
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)) {
return DistanceType::CylinderCylinder;
}
else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) {
return DistanceType::CylinderSphere;
}
else if (isFaceType(obj2, elt2, GeomAbs_Cone)) {
return DistanceType::CylinderCone;
}
else if (isFaceType(obj2, elt2, GeomAbs_Torus)) {
return DistanceType::CylinderTorus;
}
}
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)) {
return DistanceType::ConeCone;
}
else if (isFaceType(obj2, elt2, GeomAbs_Torus)) {
return DistanceType::ConeTorus;
}
else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) {
return DistanceType::ConeSphere;
}
}
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)) {
return DistanceType::TorusTorus;
}
else if (isFaceType(obj2, elt2, GeomAbs_Sphere)) {
return DistanceType::TorusSphere;
}
}
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)) {
return DistanceType::SphereSphere;
}
}
}
else if ((type1 == "Vertex" && type2 == "Face") || (type1 == "Face" && type2 == "Vertex")) {
if (type1 == "Vertex") { // Make sure face is the first.
swapJCS(joint);
std::swap(elt1, elt2);
std::swap(obj1, obj2);
}
if (isFaceType(obj1, elt1, GeomAbs_Plane)) {
return DistanceType::PointPlane;
}
else if (isFaceType(obj1, elt1, GeomAbs_Cylinder)) {
return DistanceType::PointCylinder;
}
else if (isFaceType(obj1, elt1, GeomAbs_Sphere)) {
return DistanceType::PointSphere;
}
else if (isFaceType(obj1, elt1, GeomAbs_Cone)) {
return DistanceType::PointCone;
}
else if (isFaceType(obj1, elt1, GeomAbs_Torus)) {
return DistanceType::PointTorus;
}
}
else if ((type1 == "Edge" && type2 == "Face") || (type1 == "Face" && type2 == "Edge")) {
if (type1 == "Edge") { // Make sure face is the first.
swapJCS(joint);
std::swap(elt1, elt2);
std::swap(obj1, obj2);
}
if (isEdgeType(obj2, elt2, GeomAbs_Line)) {
if (isFaceType(obj1, elt1, GeomAbs_Plane)) {
return DistanceType::LinePlane;
}
else if (isFaceType(obj1, elt1, GeomAbs_Cylinder)) {
return DistanceType::LineCylinder;
}
else if (isFaceType(obj1, elt1, GeomAbs_Sphere)) {
return DistanceType::LineSphere;
}
else if (isFaceType(obj1, elt1, GeomAbs_Cone)) {
return DistanceType::LineCone;
}
else if (isFaceType(obj1, elt1, GeomAbs_Torus)) {
return DistanceType::LineTorus;
}
}
else {
// For other curves we consider them as planes for now. Can be refined later.
if (isFaceType(obj1, elt1, GeomAbs_Plane)) {
return DistanceType::CurvePlane;
}
else if (isFaceType(obj1, elt1, GeomAbs_Cylinder)) {
return DistanceType::CurveCylinder;
}
else if (isFaceType(obj1, elt1, GeomAbs_Sphere)) {
return DistanceType::CurveSphere;
}
else if (isFaceType(obj1, elt1, GeomAbs_Cone)) {
return DistanceType::CurveCone;
}
else if (isFaceType(obj1, elt1, GeomAbs_Torus)) {
return DistanceType::CurveTorus;
}
}
}
else if ((type1 == "Vertex" && type2 == "Edge") || (type1 == "Edge" && type2 == "Vertex")) {
if (type1 == "Vertex") { // Make sure edge is the first.
swapJCS(joint);
std::swap(elt1, elt2);
std::swap(obj1, obj2);
}
if (isEdgeType(obj1, elt1, GeomAbs_Line)) { // Point on line joint.
return DistanceType::PointLine;
}
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?
return DistanceType::PointCurve;
}
}
return DistanceType::Other;
}
JointGroup* getJointGroup(const App::Part* part)
{
if (!part) {
return nullptr;
}
const auto* doc = part->getDocument();
const auto jointGroups = doc->getObjectsOfType(JointGroup::getClassTypeId());
if (jointGroups.empty()) {
return nullptr;
}
for (auto jointGroup : jointGroups) {
if (part->hasObject(jointGroup)) {
return freecad_cast<JointGroup*>(jointGroup);
}
}
return nullptr;
}
void setJointActivated(const App::DocumentObject* joint, bool val)
{
if (!joint) {
return;
}
if (auto propSuppressed = joint->getPropertyByName<App::PropertyBool>("Suppressed")) {
propSuppressed->setValue(!val);
}
}
bool getJointActivated(const App::DocumentObject* joint)
{
if (!joint) {
return false;
}
if (const auto propActivated = joint->getPropertyByName<App::PropertyBool>("Suppressed")) {
return !propActivated->getValue();
}
return false;
}
double getJointDistance(const App::DocumentObject* joint, const char* propertyName)
{
if (!joint) {
return 0.0;
}
const auto* prop = joint->getPropertyByName<App::PropertyFloat>(propertyName);
if (!prop) {
return 0.0;
}
return prop->getValue();
}
double getJointAngle(const App::DocumentObject* joint)
{
return getJointDistance(joint, "Angle");
}
double getJointDistance(const App::DocumentObject* joint)
{
return getJointDistance(joint, "Distance");
}
double getJointDistance2(const App::DocumentObject* joint)
{
return getJointDistance(joint, "Distance2");
}
JointType getJointType(const App::DocumentObject* joint)
{
if (!joint) {
return JointType::Fixed;
}
const auto* prop = joint->getPropertyByName<App::PropertyEnumeration>("JointType");
if (!prop) {
return JointType::Fixed;
}
return static_cast<JointType>(prop->getValue());
}
std::vector<std::string> getSubAsList(const App::PropertyXLinkSub* prop)
{
if (!prop) {
return {};
}
const auto subs = prop->getSubValues();
if (subs.empty()) {
return {};
}
return Base::Tools::splitSubName(subs[0]);
}
std::vector<std::string> getSubAsList(const App::DocumentObject* obj, const char* pName)
{
if (!obj) {
return {};
}
return getSubAsList(obj->getPropertyByName<App::PropertyXLinkSub>(pName));
}
std::string getElementFromProp(const App::DocumentObject* obj, const char* pName)
{
if (!obj) {
return "";
}
const auto names = getSubAsList(obj, pName);
if (names.empty()) {
return "";
}
return names.back();
}
std::string getElementTypeFromProp(const 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 (const char ch : getElementFromProp(obj, propName)) {
if (std::isalpha(ch)) {
elementType += ch;
}
}
return elementType;
}
App::DocumentObject* getObjFromProp(const App::DocumentObject* joint, const char* pName)
{
if (!joint) {
return {};
}
const auto* propObj = joint->getPropertyByName<App::PropertyLink>(pName);
if (!propObj) {
return {};
}
return propObj->getValue();
}
App::DocumentObject* getObjFromRef(const App::DocumentObject* obj, const std::string& sub)
{
if (!obj) {
return nullptr;
}
const auto* doc = obj->getDocument();
const auto names = Base::Tools::splitSubName(sub);
// Lambda function to check if the typeId is a BodySubObject
const auto isBodySubObject = [](App::DocumentObject* obj) -> bool {
// PartDesign::Point + Line + Plane + CoordinateSystem
// getViewProviderName instead of isDerivedFrom to avoid dependency on sketcher
const auto isDerivedFromVpSketch
= strcmp(obj->getViewProviderName(), "SketcherGui::ViewProviderSketch") == 0;
return isDerivedFromVpSketch || obj->isDerivedFrom<PartApp::Datum>()
|| obj->isDerivedFrom<App::DatumElement>()
|| obj->isDerivedFrom<App::LocalCoordinateSystem>();
};
// Helper function to handle PartDesign::Body objects
const auto handlePartDesignBody =
[&](App::DocumentObject* obj,
std::vector<std::string>::const_iterator it) -> App::DocumentObject* {
auto nextIt = std::next(it);
if (nextIt != names.end()) {
for (auto* obji : obj->getOutList()) {
if (*nextIt == obji->getNameInDocument() && isBodySubObject(obji)) {
// if obji is a LCS then perhaps we need to resolve one more level
if (auto* lcs = freecad_cast<App::LocalCoordinateSystem*>(obji)) {
nextIt = std::next(nextIt);
if (nextIt != names.end()) {
for (auto* objj : lcs->baseObjects()) {
if (*nextIt == objj->getNameInDocument()
&& objj->isDerivedFrom<App::DatumElement>()) {
return objj;
}
}
}
}
return obji;
}
}
}
return obj;
};
for (auto it = names.begin(); it != names.end(); ++it) {
App::DocumentObject* obj = doc->getObject(it->c_str());
if (!obj) {
return nullptr;
}
if (obj->isDerivedFrom<App::DocumentObjectGroup>()) {
continue;
}
// The last but one name should be the selected
if (std::next(it) == std::prev(names.end())) {
return obj;
}
if (obj->isDerivedFrom<App::Part>() || obj->isLinkGroup()) {
continue;
}
else if (obj->isDerivedFrom<PartDesign::Body>()) {
return handlePartDesignBody(obj, it);
}
else if (obj->isDerivedFrom<PartApp::Feature>()) {
// Primitive, fastener, gear, etc.
return obj;
}
else if (obj->isLink()) {
App::DocumentObject* linked_obj = obj->getLinkedObject();
if (linked_obj->isDerivedFrom<PartDesign::Body>()) {
auto* retObj = handlePartDesignBody(linked_obj, it);
return retObj == linked_obj ? obj : retObj;
}
else if (linked_obj->isDerivedFrom<PartApp::Feature>()) {
return obj;
}
else {
doc = linked_obj->getDocument();
continue;
}
}
}
return nullptr;
}
App::DocumentObject* getObjFromRef(const App::PropertyXLinkSub* prop)
{
if (!prop) {
return nullptr;
}
const App::DocumentObject* obj = prop->getValue();
if (!obj) {
return nullptr;
}
const std::vector<std::string> subs = prop->getSubValues();
if (subs.empty()) {
return nullptr;
}
return getObjFromRef(obj, subs[0]);
}
App::DocumentObject* getObjFromRef(const App::DocumentObject* joint, const char* pName)
{
if (!joint) {
return nullptr;
}
const auto* prop = joint->getPropertyByName<App::PropertyXLinkSub>(pName);
return getObjFromRef(prop);
}
App::DocumentObject* getLinkedObjFromRef(const App::DocumentObject* joint, const char* pObj)
{
if (!joint) {
return nullptr;
}
if (const auto* obj = getObjFromRef(joint, pObj)) {
return obj->getLinkedObject(true);
}
return nullptr;
}
App::DocumentObject* getMovingPartFromRef(
const AssemblyObject* assemblyObject,
App::DocumentObject* obj,
const std::string& sub
)
{
if (!obj) {
return nullptr;
}
auto* doc = obj->getDocument();
auto names = Base::Tools::splitSubName(sub);
names.insert(names.begin(), obj->getNameInDocument());
bool assemblyPassed = false;
for (const auto& objName : names) {
obj = doc->getObject(objName.c_str());
if (!obj) {
continue;
}
if (obj->isLink()) { // update the document if necessary for next object
doc = obj->getLinkedObject()->getDocument();
}
if (obj == assemblyObject) {
// We make sure we pass the assembly for cases like part.assembly.part.body
assemblyPassed = true;
continue;
}
if (!assemblyPassed) {
continue;
}
if (obj->isDerivedFrom<App::DocumentObjectGroup>()) {
continue; // we ignore groups.
}
if (obj->isLinkGroup()) {
continue;
}
// We ignore dynamic sub-assemblies.
if (obj->isDerivedFrom<Assembly::AssemblyLink>()) {
const auto* pRigid = obj->getPropertyByName<App::PropertyBool>("Rigid");
if (pRigid && !pRigid->getValue()) {
continue;
}
}
return obj;
}
return nullptr;
}
App::DocumentObject* getMovingPartFromRef(
const AssemblyObject* assemblyObject,
App::PropertyXLinkSub* prop
)
{
if (!prop) {
return nullptr;
}
App::DocumentObject* obj = prop->getValue();
if (!obj) {
return nullptr;
}
const std::vector<std::string> subs = prop->getSubValues();
if (subs.empty()) {
return nullptr;
}
return getMovingPartFromRef(assemblyObject, obj, subs[0]);
}
App::DocumentObject* getMovingPartFromRef(
const AssemblyObject* assemblyObject,
App::DocumentObject* joint,
const char* pName
)
{
if (!joint) {
return nullptr;
}
auto* prop = joint->getPropertyByName<App::PropertyXLinkSub>(pName);
return getMovingPartFromRef(assemblyObject, prop);
}
void syncPlacements(App::DocumentObject* src, App::DocumentObject* to)
{
auto* plcPropSource = dynamic_cast<App::PropertyPlacement*>(src->getPropertyByName("Placement"));
auto* plcPropLink = dynamic_cast<App::PropertyPlacement*>(to->getPropertyByName("Placement"));
if (plcPropSource && plcPropLink) {
if (!plcPropSource->getValue().isSame(plcPropLink->getValue())) {
plcPropLink->setValue(plcPropSource->getValue());
}
}
}
} // namespace Assembly