/*************************************************************************** * Copyright (c) 2011 Juergen Riegel * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library 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 Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include "App/Datums.h" #include #include #include #include #include #include "Feature.h" #include "FeaturePy.h" #include "Body.h" #include "ShapeBinder.h" #include FC_LOG_LEVEL_INIT("PartDesign", true, true) namespace PartDesign { bool getPDRefineModelParameter() { Base::Reference hGrp = App::GetApplication() .GetUserParameter() .GetGroup("BaseApp") ->GetGroup("Preferences") ->GetGroup("Mod/PartDesign"); return hGrp->GetBool("RefineModel", true); } // ------------------------------------------------------------------------------------------------ PROPERTY_SOURCE(PartDesign::Feature, Part::Feature) Feature::Feature() { ADD_PROPERTY(BaseFeature, (nullptr)); ADD_PROPERTY_TYPE( _Body, (nullptr), "Base", (App::PropertyType)(App::Prop_ReadOnly | App::Prop_Hidden | App::Prop_Output | App::Prop_Transient), 0 ); ADD_PROPERTY(SuppressedShape, (TopoShape())); Placement.setStatus(App::Property::Hidden, true); BaseFeature.setStatus(App::Property::Hidden, true); App::SuppressibleExtension::initExtension(this); Part::PreviewExtension::initExtension(this); } App::DocumentObjectExecReturn* Feature::recompute() { setMaterialToBodyMaterial(); SuppressedShape.setValue(TopoShape()); if (!Suppressed.getValue()) { return Part::Feature::recompute(); } bool failed = false; try { std::unique_ptr ret(Part::Feature::recompute()); if (ret) { throw Base::RuntimeError(ret->Why); } } catch (Base::AbortException&) { throw; } catch (Base::Exception& e) { failed = true; e.reportException(); FC_ERR("Failed to recompute suppressed feature " << getFullName()); } Shape.setValue(getBaseTopoShape(true)); if (!failed) { updateSuppressedShape(); } return App::DocumentObject::StdReturn; } App::DocumentObjectExecReturn* Feature::recomputePreview() { updatePreviewShape(); return StdReturn; } void Feature::setMaterialToBodyMaterial() { auto body = getFeatureBody(); if (body) { // Ensure the part has the same material as the body auto feature = dynamic_cast(body); if (feature) { copyMaterial(feature); } } } void Feature::updateSuppressedShape() { TopoShape res(getID()); TopoShape shape = Shape.getShape(); shape.setPlacement(Base::Placement()); std::vector generated; if (!shape.isNull()) { unsigned count = shape.countSubShapes(TopAbs_FACE); for (unsigned i = 1; i <= count; ++i) { Data::MappedName mapped = shape.getMappedName(Data::IndexedName::fromConst("Face", i)); if (mapped && shape.isElementGenerated(mapped)) { generated.push_back(shape.getSubTopoShape(TopAbs_FACE, i)); } } } if (!generated.empty()) { res.makeElementCompound(generated); res.setPlacement(Placement.getValue()); } SuppressedShape.setValue(res); } short Feature::mustExecute() const { if (BaseFeature.isTouched()) { return 1; } return Part::Feature::mustExecute(); } TopoShape Feature::getSolid(const TopoShape& shape) const { if (shape.isNull()) { throw Part::NullShapeException("Null shape"); } // If single solid rule is not enforced we simply return the shape as is if (singleSolidRuleMode() != Feature::SingleSolidRuleMode::Enforced) { return shape; } int count = shape.countSubShapes(TopAbs_SOLID); if (count) { auto res = shape.getSubTopoShape(TopAbs_SOLID, 1); res.fixSolidOrientation(); return res; } return shape; } void Feature::onChanged(const App::Property* prop) { if (!this->isRestoring() && this->getDocument() && !this->getDocument()->isPerformingTransaction()) { if (prop == &Visibility || prop == &BaseFeature) { auto body = Body::findBodyOf(this); if (body) { if (prop == &BaseFeature && BaseFeature.getValue()) { int idx = -1; body->Group.find(this->getNameInDocument(), &idx); int baseidx = -1; body->Group.find(BaseFeature.getValue()->getNameInDocument(), &idx); if (idx >= 0 && baseidx >= 0 && baseidx + 1 != idx) { body->insertObject(BaseFeature.getValue(), this); } } } } else if (prop == &ShapeMaterial) { auto body = Body::findBodyOf(this); if (body) { if (body->ShapeMaterial.getValue().getUUID() != ShapeMaterial.getValue().getUUID()) { body->ShapeMaterial.setValue(ShapeMaterial.getValue()); } } } else if (prop == &Suppressed) { if (Suppressed.getValue()) { SuppressedPlacement = Placement.getValue(); } else { Placement.setValue(SuppressedPlacement); SuppressedPlacement = Base::Placement(); } } } Part::Feature::onChanged(prop); } int Feature::countSolids(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) { int result = 0; if (shape.IsNull()) { return result; } TopExp_Explorer xp; xp.Init(shape, type); for (; xp.More(); xp.Next()) { result++; } return result; } TopoShape Feature::fixSolids(const TopoShape& solids) { if (solids.isNull()) { return solids; } std::vector fixSolids; TopExp_Explorer xp; xp.Init(solids.getShape(), TopAbs_SOLID); for (; xp.More(); xp.Next()) { TopoDS_Solid solid = TopoDS::Solid(xp.Current()); BRepCheck_Solid bs(solid); if (bs.IsStatusOnShape(solid)) { const auto& listOfStatus = bs.StatusOnShape(solid); if (listOfStatus.Contains(BRepCheck_EnclosedRegion)) { fixSolids.emplace_back(solid); } } } if (fixSolids.empty()) { return solids; } TopoDS_Compound comp; TopoDS_Builder bb; bb.MakeCompound(comp); for (const TopoDS_Solid& it : fixSolids) { ShapeFix_Solid fix(it); fix.Perform(); bb.Add(comp, fix.Solid()); } TopoShape fixShape(comp); return fixShape; } bool Feature::isSingleSolidRuleSatisfied(const TopoDS_Shape& shape, TopAbs_ShapeEnum type) { if (singleSolidRuleMode() == Feature::SingleSolidRuleMode::Disabled) { return true; } int solidCount = countSolids(shape, type); return solidCount <= 1; } Feature::SingleSolidRuleMode Feature::singleSolidRuleMode() const { auto body = getFeatureBody(); // When the feature is not part of an body (which should not happen) let's stay with the default if (!body) { return SingleSolidRuleMode::Enforced; } auto areCompoundSolidsAllowed = body->AllowCompound.getValue(); return areCompoundSolidsAllowed ? SingleSolidRuleMode::Disabled : SingleSolidRuleMode::Enforced; } const gp_Pnt Feature::getPointFromFace(const TopoDS_Face& f) { if (!f.Infinite()) { TopExp_Explorer exp; exp.Init(f, TopAbs_VERTEX); if (exp.More()) { return BRep_Tool::Pnt(TopoDS::Vertex(exp.Current())); } // Else try the other method } // TODO: Other method, e.g. intersect X,Y,Z axis with the (unlimited?) face? // Or get a "corner" point if the face is limited? throw Base::NotImplementedError("getPointFromFace(): Not implemented yet for this case"); } Part::Feature* Feature::getBaseObject(bool silent) const { App::DocumentObject* BaseLink = BaseFeature.getValue(); Part::Feature* BaseObject = nullptr; const char* err = nullptr; if (BaseLink) { if (BaseLink->isDerivedFrom()) { BaseObject = static_cast(BaseLink); } if (!BaseObject) { err = "No base feature linked"; } } else { err = "Base property not set"; } // If the function not in silent mode throw the exception describing the error if (!silent && err) { throw Base::RuntimeError(err); } return BaseObject; } const TopoDS_Shape& Feature::getBaseShape() const { const Part::Feature* BaseObject = getBaseObject(); if (!BaseObject) { throw Base::ValueError("Base feature's shape is not defined"); } if (BaseObject->isDerivedFrom() || BaseObject->isDerivedFrom()) { throw Base::ValueError("Base shape of shape binder cannot be used"); } const TopoDS_Shape& result = BaseObject->Shape.getValue(); if (result.IsNull()) { throw Base::ValueError("Base feature's shape is invalid"); } TopExp_Explorer xp(result, TopAbs_SOLID); if (!xp.More()) { throw Base::ValueError("Base feature's shape is not a solid"); } return result; } Part::TopoShape Feature::getBaseTopoShape(bool silent) const { Part::TopoShape result; const Part::Feature* BaseObject = getBaseObject(silent); if (!BaseObject) { return result; } if (BaseObject != BaseFeature.getValue()) { auto body = getFeatureBody(); if (!body) { if (silent) { return result; } throw Base::RuntimeError("Missing container body"); } if (BaseObject->isDerivedFrom() || BaseObject->isDerivedFrom()) { if (silent) { return result; } throw Base::ValueError("Base shape of shape binder cannot be used"); } } result = BaseObject->Shape.getShape(); if (!silent) { if (result.isNull()) { throw Base::ValueError("Base feature's TopoShape is invalid"); } if (!result.hasSubShape(TopAbs_SOLID)) { throw Base::ValueError("Base feature's shape is not a solid"); } } else if (!result.hasSubShape(TopAbs_SOLID)) { result.setShape(TopoDS_Shape()); } return result; } void Feature::getGeneratedShapes( std::vector& faces, std::vector& edges, std::vector& vertices ) const { static const auto addAllSubShapesToSet = [](const Part::TopoShape& shape, const Part::TopoShape& face, TopAbs_ShapeEnum type, std::set& set) { for (auto& subShape : face.getSubShapes(type)) { if (int subShapeId = shape.findShape(subShape); subShapeId > 0) { set.insert(subShapeId); } } }; Part::TopoShape shape = Shape.getShape(); std::set edgeSet; std::set vertexSet; int count = shape.countSubShapes(TopAbs_FACE); for (int faceId = 1; faceId <= count; ++faceId) { if (Data::MappedName mapped = shape.getMappedName(Data::IndexedName::fromConst("Face", faceId)); shape.isElementGenerated(mapped)) { faces.push_back(faceId); Part::TopoShape face = shape.getSubTopoShape(TopAbs_FACE, faceId); addAllSubShapesToSet(shape, face, TopAbs_EDGE, edgeSet); addAllSubShapesToSet(shape, face, TopAbs_VERTEX, vertexSet); } } std::ranges::copy(edgeSet, std::back_inserter(edges)); std::ranges::copy(vertexSet, std::back_inserter(vertices)); } void Feature::updatePreviewShape() { // no-op } PyObject* Feature::getPyObject() { if (PythonObject.is(Py::_None())) { // ref counter is set to 1 PythonObject = Py::Object(new FeaturePy(this), true); } return Py::new_reference_to(PythonObject); } bool Feature::isDatum(const App::DocumentObject* feature) { return feature->isDerivedFrom() || feature->isDerivedFrom(); } gp_Pln Feature::makePlnFromPlane(const App::DocumentObject* obj) { const App::GeoFeature* plane = static_cast(obj); if (!plane) { throw Base::ValueError("Feature: Null object"); } Base::Vector3d pos = plane->Placement.getValue().getPosition(); Base::Rotation rot = plane->Placement.getValue().getRotation(); Base::Vector3d normal(0, 0, 1); rot.multVec(normal, normal); return gp_Pln(gp_Pnt(pos.x, pos.y, pos.z), gp_Dir(normal.x, normal.y, normal.z)); } // TODO: Toponaming April 2024 Deprecated in favor of TopoShape method. Remove when possible. TopoDS_Shape Feature::makeShapeFromPlane(const App::DocumentObject* obj) { BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); if (!builder.IsDone()) { throw Base::CADKernelError("Feature: Could not create shape from base plane"); } return builder.Shape(); } TopoShape Feature::makeTopoShapeFromPlane(const App::DocumentObject* obj) { BRepBuilderAPI_MakeFace builder(makePlnFromPlane(obj)); if (!builder.IsDone()) { throw Base::CADKernelError("Feature: Could not create shape from base plane"); } return TopoShape(obj->getID(), nullptr, builder.Shape()); } Body* Feature::getFeatureBody() const { auto body = freecad_cast(_Body.getValue()); if (body) { return body; } auto list = getInList(); for (auto in : list) { if (in->isDerivedFrom() && // is Body? static_cast(in)->hasObject(this)) { // is part of this Body? return static_cast(in); } } return nullptr; } App::DocumentObject* Feature::getSubObject( const char* subname, PyObject** pyObj, Base::Matrix4D* pmat, bool transform, int depth ) const { if (subname && subname != Data::findElementName(subname)) { const char* dot = strchr(subname, '.'); if (dot) { auto body = PartDesign::Body::findBodyOf(this); if (body) { auto feat = body->Group.findUsingMap(std::string(subname, dot)); if (feat) { Base::Matrix4D _mat; if (!transform) { // Normally the parent object is supposed to transform // the sub-object using its own placement. So, if no // transform is requested, (i.e. no parent // transformation), we just need to NOT apply the // transformation. // // But PartDesign features (including sketch) are // supposed to be contained inside a body. It makes // little sense to transform its sub-object. So if 'no // transform' is requested, we need to actively apply // an inverse transform. _mat = Placement.getValue().inverse().toMatrix(); if (pmat) { *pmat *= _mat; } else { pmat = &_mat; } } return feat->getSubObject(dot + 1, pyObj, pmat, true, depth + 1); } } } } return Part::Feature::getSubObject(subname, pyObj, pmat, transform, depth); } } // namespace PartDesign namespace App { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(PartDesign::FeaturePython, PartDesign::Feature) template<> const char* PartDesign::FeaturePython::getViewProviderName() const { return "PartDesignGui::ViewProviderPython"; } template<> PyObject* PartDesign::FeaturePython::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 PartDesignExport FeaturePythonT; } // namespace App