/*************************************************************************** * Copyright (c) 2010 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 "FeatureDressUp.h" #include #include #include #include "Mod/Part/App/TopoShapeMapper.h" FC_LOG_LEVEL_INIT("PartDesign", true, true) using namespace PartDesign; namespace PartDesign { PROPERTY_SOURCE(PartDesign::DressUp, PartDesign::FeatureAddSub) DressUp::DressUp() { ADD_PROPERTY(Base, (nullptr)); Placement.setStatus(App::Property::ReadOnly, true); ADD_PROPERTY_TYPE( SupportTransform, (false), "Base", App::Prop_None, "Include the base additive/subtractive shape when used in pattern features.\n" "If disabled, only the dressed part of the shape is used for patterning." ); AddSubShape.setStatus(App::Property::Output, true); } short DressUp::mustExecute() const { if (Base.getValue() && Base.getValue()->isTouched()) { return 1; } return PartDesign::FeatureAddSub::mustExecute(); } void DressUp::positionByBaseFeature() { Part::Feature* base = static_cast(BaseFeature.getValue()); if (base && base->isDerivedFrom()) { this->Placement.setValue(base->Placement.getValue()); } } Part::Feature* DressUp::getBaseObject(bool silent) const { Part::Feature* rv = Feature::getBaseObject(/* silent = */ true); if (rv) { return rv; } const char* err = nullptr; App::DocumentObject* base = Base.getValue(); if (base) { if (base->isDerivedFrom()) { rv = static_cast(base); } else { err = "Linked object is not a Part object"; } } else { err = "No Base object linked"; } if (!silent && err) { throw Base::RuntimeError(err); } return rv; } void DressUp::getContinuousEdges(Part::TopoShape TopShape, std::vector& SubNames) { std::vector FaceNames; getContinuousEdges(TopShape, SubNames, FaceNames); } void DressUp::getContinuousEdges( Part::TopoShape TopShape, std::vector& SubNames, std::vector& FaceNames ) { TopTools_IndexedMapOfShape mapOfEdges; TopTools_IndexedDataMapOfShapeListOfShape mapEdgeFace; TopExp::MapShapesAndAncestors(TopShape.getShape(), TopAbs_EDGE, TopAbs_FACE, mapEdgeFace); TopExp::MapShapes(TopShape.getShape(), TopAbs_EDGE, mapOfEdges); unsigned int i = 0; while (i < SubNames.size()) { std::string aSubName = static_cast(SubNames.at(i)); if (aSubName.compare(0, 4, "Edge") == 0) { TopoDS_Edge edge = TopoDS::Edge(TopShape.getSubShape(aSubName.c_str())); const TopTools_ListOfShape& los = mapEdgeFace.FindFromKey(edge); if (los.Extent() != 2) { SubNames.erase(SubNames.begin() + i); continue; } const TopoDS_Shape& face1 = los.First(); const TopoDS_Shape& face2 = los.Last(); GeomAbs_Shape cont = BRep_Tool::Continuity(TopoDS::Edge(edge), TopoDS::Face(face1), TopoDS::Face(face2)); if (cont != GeomAbs_C0) { SubNames.erase(SubNames.begin() + i); continue; } i++; } else if (aSubName.compare(0, 4, "Face") == 0) { TopoDS_Face face = TopoDS::Face(TopShape.getSubShape(aSubName.c_str())); TopTools_IndexedMapOfShape mapOfFaces; TopExp::MapShapes(face, TopAbs_EDGE, mapOfFaces); for (int j = 1; j <= mapOfFaces.Extent(); ++j) { TopoDS_Edge edge = TopoDS::Edge(mapOfFaces.FindKey(j)); int id = mapOfEdges.FindIndex(edge); std::stringstream buf; buf << "Edge"; buf << id; if (std::ranges::find(SubNames, buf.str()) == SubNames.end()) { SubNames.push_back(buf.str()); } } FaceNames.emplace_back(aSubName.c_str()); SubNames.erase(SubNames.begin() + i); } // empty name or any other sub-element else { SubNames.erase(SubNames.begin() + i); } } } std::vector DressUp::getContinuousEdges(const TopoShape& shape) { std::vector ret; std::unordered_set shapeSet; auto addEdge = [&](const TopoDS_Shape& subshape, const std::string& ref) { if (!shapeSet.insert(subshape).second) { return; } auto faces = shape.findAncestorsShapes(subshape, TopAbs_FACE); if (faces.size() != 2) { FC_WARN(getFullName() << ": skip edge " << ref << " with less two attaching faces"); return; } const TopoDS_Shape& face1 = faces.front(); const TopoDS_Shape& face2 = faces.back(); GeomAbs_Shape cont = BRep_Tool::Continuity(TopoDS::Edge(subshape), TopoDS::Face(face1), TopoDS::Face(face2)); if (cont != GeomAbs_C0) { FC_WARN(getFullName() << ": skip edge " << ref << " that is not C0 continuous"); return; } ret.push_back(subshape); }; for (const auto& v : Base.getShadowSubs()) { TopoDS_Shape subshape; const auto& ref = v.newName.size() ? v.newName : v.oldName; subshape = shape.getSubShape(ref.c_str(), true); if (subshape.IsNull()) { FC_THROWM(Base::CADKernelError, "Invalid edge link: " << ref); } if (subshape.ShapeType() == TopAbs_EDGE) { addEdge(subshape, ref); } else if (subshape.ShapeType() == TopAbs_FACE || subshape.ShapeType() == TopAbs_WIRE) { for (TopExp_Explorer exp(subshape, TopAbs_EDGE); exp.More(); exp.Next()) { addEdge(exp.Current(), std::string()); } } else { FC_WARN( getFullName() << ": skip invalid shape '" << ref << "' with type " << TopoShape::shapeName(subshape.ShapeType()) ); } } return ret; } std::vector DressUp::getFaces(const TopoShape& shape) { std::vector ret; const auto& vals = Base.getSubValues(); const auto& subs = Base.getShadowSubs(); size_t i = 0; for (auto& val : vals) { if (!boost::starts_with(val, "Face")) { continue; } auto& sub = subs[i++]; auto& ref = sub.newName.size() ? sub.newName : val; TopoShape subshape; try { subshape = shape.getSubTopoShape(ref.c_str()); } catch (...) { } if (subshape.isNull()) { FC_ERR(getFullName() << ": invalid face reference '" << ref << "'"); throw Part::NullShapeException("Invalid Invalid face link"); } if (subshape.shapeType() != TopAbs_FACE) { FC_WARN( getFullName() << ": skip invalid shape '" << ref << "' with type " << subshape.shapeName() ); continue; } ret.push_back(subshape); } return ret; } void DressUp::onChanged(const App::Property* prop) { // the BaseFeature property should track the Base and vice-versa as long as // the feature is inside a body (aka BaseFeature is nonzero) if (prop == &BaseFeature) { if (BaseFeature.getValue() && Base.getValue() && Base.getValue() != BaseFeature.getValue()) { auto subs = Base.getSubValues(false); auto shadows = Base.getShadowSubs(); Base.setValue(BaseFeature.getValue(), std::move(subs), std::move(shadows)); } } else if (prop == &Base) { // track the vice-versa changes if (BaseFeature.getValue() && Base.getValue() != BaseFeature.getValue()) { BaseFeature.setValue(Base.getValue()); } } else if (prop == &Shape || prop == &SupportTransform) { if (!getDocument()->testStatus(App::Document::Restoring) && !getDocument()->isPerformingTransaction()) { // AddSubShape in DressUp acts as a shape cache. And here we shall // invalidate the cache upon changes in Shape. Other features // (currently only feature Transformed) shall call getAddSubShape() // to rebuild the cache. This allow us to perform expensive // calculation of AddSubShape only when necessary. AddSubShape.setValue(Part::TopoShape()); } } Feature::onChanged(prop); } void DressUp::getAddSubShape(Part::TopoShape& addShape, Part::TopoShape& subShape) { Part::TopoShape res = AddSubShape.getShape(); if (res.isNull()) { try { std::vector shapes; Part::TopoShape shape = Shape.getShape(); shape.setPlacement(Base::Placement()); FeatureAddSub* base = nullptr; if (SupportTransform.getValue()) { // SupportTransform means transform the support together with // the dressing. So we need to find the previous support // feature (which must be of type FeatureAddSub), and skipping // any consecutive DressUp in-between. for (Feature* current = this;; current = static_cast(base)) { base = freecad_cast(current->getBaseObject(true)); if (!base) { FC_THROWM( Base::CADKernelError, "Cannot find additive or subtractive support for " << getFullName() ); } if (!base->isDerivedFrom()) { break; } } } Part::TopoShape baseShape; if (base) { baseShape = base->getBaseTopoShape(true); baseShape.move(base->getLocation().Inverted()); if (base->getAddSubType() == Additive) { if (!baseShape.isNull() && baseShape.hasSubShape(TopAbs_SOLID)) { shapes.emplace_back(shape.makeElementCut(baseShape.getShape())); } else { shapes.push_back(shape); } } else { BRep_Builder builder; TopoDS_Compound comp; builder.MakeCompound(comp); // push an empty compound to indicate null additive shape shapes.emplace_back(comp); if (!baseShape.isNull() && baseShape.hasSubShape(TopAbs_SOLID)) { shapes.emplace_back(baseShape.makeElementCut(shape.getShape())); } else { shapes.push_back(shape); } } } else { baseShape = getBaseTopoShape(); baseShape.move(getLocation().Inverted()); shapes.emplace_back(shape.makeElementCut(baseShape.getShape())); shapes.emplace_back(baseShape.makeElementCut(shape.getShape())); } // Make a compound to contain both additive and subtractive shape, // bceause a dressing (e.g. a fillet) can either be additive or // subtractive. And the dressup feature can contain mixture of both. AddSubShape.setValue(Part::TopoShape().makeElementCompound(shapes)); } catch (Standard_Failure& e) { FC_THROWM( Base::CADKernelError, "Failed to calculate AddSub shape: " << e.GetMessageString() ); } res = AddSubShape.getShape(); } if (res.isNull()) { throw Part::NullShapeException("Null AddSub shape"); } if (res.getShape().ShapeType() != TopAbs_COMPOUND) { addShape = res; } else { int count = res.countSubShapes(TopAbs_SHAPE); if (!count) { throw Part::NullShapeException("Null AddSub shape"); } if (count) { Part::TopoShape s = res.getSubTopoShape(TopAbs_SHAPE, 1); if (!s.isNull() && s.hasSubShape(TopAbs_SOLID)) { addShape = s; } } if (count > 1) { Part::TopoShape s = res.getSubTopoShape(TopAbs_SHAPE, 2); if (!s.isNull() && s.hasSubShape(TopAbs_SOLID)) { subShape = s; } } } } void DressUp::updatePreviewShape() { auto shape = Shape.getShape(); auto baseFeature = freecad_cast(BaseFeature.getValue()); if (!baseFeature || baseFeature->Shape.getShape().isNull()) { PreviewShape.setValue(Shape.getShape()); return; } std::vector faces, edges, vertices; getGeneratedShapes(faces, edges, vertices); if (faces.empty()) { PreviewShape.setValue(TopoDS_Shape()); return; } shape.setPlacement(Base::Placement()); BRep_Builder builder; TopoDS_Compound comp; builder.MakeCompound(comp); for (int faceId : faces) { builder.Add(comp, shape.getSubShape(TopAbs_FACE, faceId)); } Part::TopoShape preview(comp); preview.mapSubElement(shape); PreviewShape.setValue(preview); } } // namespace PartDesign