| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| |
|
| | #include <BRep_Tool.hxx>
|
| | #include <BRepBuilderAPI_MakeFace.hxx>
|
| | #include <BRepCheck_Solid.hxx>
|
| | #include <BRepCheck_Status.hxx>
|
| | #include <gp_Pln.hxx>
|
| | #include <gp_Pnt.hxx>
|
| | #include <ShapeFix_Solid.hxx>
|
| | #include <Standard_Failure.hxx>
|
| | #include <TopExp_Explorer.hxx>
|
| | #include <TopoDS.hxx>
|
| | #include <TopoDS_Builder.hxx>
|
| |
|
| |
|
| | #include "App/Datums.h"
|
| | #include <App/Document.h>
|
| | #include <App/DocumentObject.h>
|
| | #include <App/ElementNamingUtils.h>
|
| | #include <App/FeaturePythonPyImp.h>
|
| | #include <Base/Console.h>
|
| |
|
| | #include "Feature.h"
|
| | #include "FeaturePy.h"
|
| | #include "Body.h"
|
| | #include "ShapeBinder.h"
|
| |
|
| | #include <BRep_Builder.hxx>
|
| |
|
| | FC_LOG_LEVEL_INIT("PartDesign", true, true)
|
| |
|
| |
|
| | namespace PartDesign
|
| | {
|
| |
|
| | bool getPDRefineModelParameter()
|
| | {
|
| | Base::Reference<ParameterGrp> 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<App::DocumentObjectExecReturn> 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) {
|
| |
|
| | auto feature = dynamic_cast<Part::Feature*>(body);
|
| | if (feature) {
|
| | copyMaterial(feature);
|
| | }
|
| | }
|
| | }
|
| |
|
| | void Feature::updateSuppressedShape()
|
| | {
|
| | TopoShape res(getID());
|
| | TopoShape shape = Shape.getShape();
|
| | shape.setPlacement(Base::Placement());
|
| | std::vector<TopoShape> 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 (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<TopoDS_Solid> 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();
|
| |
|
| |
|
| | 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()));
|
| | }
|
| |
|
| | }
|
| |
|
| |
|
| |
|
| | 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<Part::Feature>()) {
|
| | BaseObject = static_cast<Part::Feature*>(BaseLink);
|
| | }
|
| | if (!BaseObject) {
|
| | err = "No base feature linked";
|
| | }
|
| | }
|
| | else {
|
| | err = "Base property not set";
|
| | }
|
| |
|
| |
|
| | 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<PartDesign::ShapeBinder>()
|
| | || BaseObject->isDerivedFrom<PartDesign::SubShapeBinder>()) {
|
| | 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<PartDesign::ShapeBinder>()
|
| | || BaseObject->isDerivedFrom<PartDesign::SubShapeBinder>()) {
|
| | 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<int>& faces,
|
| | std::vector<int>& edges,
|
| | std::vector<int>& vertices
|
| | ) const
|
| | {
|
| | static const auto addAllSubShapesToSet = [](const Part::TopoShape& shape,
|
| | const Part::TopoShape& face,
|
| | TopAbs_ShapeEnum type,
|
| | std::set<int>& 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<int> edgeSet;
|
| | std::set<int> 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()
|
| | {
|
| |
|
| | }
|
| |
|
| | PyObject* Feature::getPyObject()
|
| | {
|
| | if (PythonObject.is(Py::_None())) {
|
| |
|
| | PythonObject = Py::Object(new FeaturePy(this), true);
|
| | }
|
| | return Py::new_reference_to(PythonObject);
|
| | }
|
| |
|
| | bool Feature::isDatum(const App::DocumentObject* feature)
|
| | {
|
| | return feature->isDerivedFrom<App::DatumElement>() || feature->isDerivedFrom<Part::Datum>();
|
| | }
|
| |
|
| | gp_Pln Feature::makePlnFromPlane(const App::DocumentObject* obj)
|
| | {
|
| | const App::GeoFeature* plane = static_cast<const App::GeoFeature*>(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));
|
| | }
|
| |
|
| |
|
| | 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*>(_Body.getValue());
|
| | if (body) {
|
| | return body;
|
| | }
|
| |
|
| | auto list = getInList();
|
| | for (auto in : list) {
|
| | if (in->isDerivedFrom<Body>() &&
|
| | static_cast<Body*>(in)->hasObject(this)) {
|
| |
|
| | return static_cast<Body*>(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) {
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| |
|
| | _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 App
|
| | {
|
| |
|
| | 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())) {
|
| |
|
| | PythonObject = Py::Object(new FeaturePythonPyT<PartDesign::FeaturePy>(this), true);
|
| | }
|
| | return Py::new_reference_to(PythonObject);
|
| | }
|
| |
|
| |
|
| |
|
| | template class PartDesignExport FeaturePythonT<PartDesign::Feature>;
|
| | }
|
| |
|