/****************************************************************************** * Copyright (c) 2012 Jan Rheinländer * * * * 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 "PreCompiled.h" #ifndef _PreComp_ # include # include # include # include # include # include # include # include # include # include # include #endif #ifndef FC_DEBUG #include #endif #include "FeatureTransformed.h" #include "FeatureMultiTransform.h" #include "FeatureAddSub.h" #include "FeatureMirrored.h" #include "FeatureLinearPattern.h" #include "FeaturePolarPattern.h" #include "FeatureSketchBased.h" #include "Body.h" #include #include #include #include #include #include using namespace PartDesign; namespace PartDesign { const char* Transformed::OverlapEnums[] = { "Detect", "Overlap mode", "Non-overlap mode", NULL}; PROPERTY_SOURCE(PartDesign::Transformed, PartDesign::Feature) Transformed::Transformed() { ADD_PROPERTY(Originals,(0)); Originals.setSize(0); Placement.setStatus(App::Property::ReadOnly, true); ADD_PROPERTY_TYPE(Refine,(0),"Part Design",(App::PropertyType)(App::Prop_None),"Refine shape (clean up redundant edges) after adding/subtracting"); ADD_PROPERTY_TYPE(Overlap, (0L), "Transform", App::Prop_None, "Feature overlapping behaviour"); Overlap.setEnums(OverlapEnums); //init Refine property Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/PartDesign"); this->Refine.setValue(hGrp->GetBool("RefineModel", false)); } void Transformed::positionBySupport(void) { // TODO May be here better to throw exception (silent=false) (2015-07-27, Fat-Zer) Part::Feature *support = getBaseObject(/* silent =*/ true); if (support) this->Placement.setValue(support->Placement.getValue()); } Part::Feature* Transformed::getBaseObject(bool silent) const { Part::Feature *rv = Feature::getBaseObject(/* silent = */ true); if (rv) { return rv; } const char* err = nullptr; const std::vector & originals = Originals.getValues(); // NOTE: may be here supposed to be last origin but in order to keep the old behaviour keep here first App::DocumentObject* firstOriginal = originals.empty() ? NULL : originals.front(); if (firstOriginal) { if(firstOriginal->isDerivedFrom(Part::Feature::getClassTypeId())) { rv = static_cast(firstOriginal); } else { err = "Transformation feature Linked object is not a Part object"; } } else { err = "No originals linked to the transformed feature."; } if (!silent && err) { throw Base::RuntimeError(err); } return rv; } App::DocumentObject* Transformed::getSketchObject() const { std::vector originals = Originals.getValues(); if (!originals.empty() && originals.front()->getTypeId().isDerivedFrom(PartDesign::ProfileBased::getClassTypeId())) { return (static_cast(originals.front()))->getVerifiedSketch(true); } else if (!originals.empty() && originals.front()->getTypeId().isDerivedFrom(PartDesign::FeatureAddSub::getClassTypeId())) { return NULL; } else if (this->getTypeId().isDerivedFrom(LinearPattern::getClassTypeId())) { // if Originals is empty then try the linear pattern's Direction property const LinearPattern* pattern = static_cast(this); return pattern->Direction.getValue(); } else if (this->getTypeId().isDerivedFrom(PolarPattern::getClassTypeId())) { // if Originals is empty then try the polar pattern's Axis property const PolarPattern* pattern = static_cast(this); return pattern->Axis.getValue(); } else if (this->getTypeId().isDerivedFrom(Mirrored::getClassTypeId())) { // if Originals is empty then try the mirror pattern's MirrorPlane property const Mirrored* pattern = static_cast(this); return pattern->MirrorPlane.getValue(); } else { return 0; } } void Transformed::Restore(Base::XMLReader &reader) { PartDesign::Feature::Restore(reader); } void Transformed::handleChangedPropertyType(Base::XMLReader &reader, const char * TypeName, App::Property * prop) { // The property 'Angle' of PolarPattern has changed from PropertyFloat // to PropertyAngle and the property 'Length' has changed to PropertyLength. Base::Type inputType = Base::Type::fromName(TypeName); if (prop->getTypeId().isDerivedFrom(App::PropertyFloat::getClassTypeId()) && inputType.isDerivedFrom(App::PropertyFloat::getClassTypeId())) { // Do not directly call the property's Restore method in case the implementation // has changed. So, create a temporary PropertyFloat object and assign the value. App::PropertyFloat floatProp; floatProp.Restore(reader); static_cast(prop)->setValue(floatProp.getValue()); } else { PartDesign::Feature::handleChangedPropertyType(reader, TypeName, prop); } } short Transformed::mustExecute() const { if (Originals.isTouched()) return 1; return PartDesign::Feature::mustExecute(); } App::DocumentObjectExecReturn *Transformed::execute(void) { std::string overlapMode = Overlap.getValueAsString(); bool overlapDetectionMode = overlapMode == "Detect"; #ifndef FC_DEBUG std::clock_t start0; start0 = std::clock(); #endif std::vector originals = Originals.getValues(); if (originals.empty()) // typically InsideMultiTransform return App::DocumentObject::StdReturn; if(!this->BaseFeature.getValue()) { auto body = getFeatureBody(); if(body) { body->setBaseProperty(this); } } this->positionBySupport(); // get transformations from subclass by calling virtual method std::vector transformations; try { std::list t_list = getTransformations(originals); transformations.insert(transformations.end(), t_list.begin(), t_list.end()); } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } if (transformations.empty()) return App::DocumentObject::StdReturn; // No transformations defined, exit silently // Get the support Part::Feature* supportFeature; try { supportFeature = getBaseObject(); } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } const Part::TopoShape& supportTopShape = supportFeature->Shape.getShape(); if (supportTopShape.getShape().IsNull()) return new App::DocumentObjectExecReturn("Cannot transform invalid support shape"); // create an untransformed copy of the support shape Part::TopoShape supportShape(supportTopShape); gp_Trsf trsfInv = supportShape.getShape().Location().Transformation().Inverted(); supportShape.setTransform(Base::Matrix4D()); TopoDS_Shape support = supportShape.getShape(); // NOTE: It would be possible to build a compound from all original addShapes/subShapes and then // transform the compounds as a whole. But we choose to apply the transformations to each // Original separately. This way it is easier to discover what feature causes a fuse/cut // to fail. The downside is that performance suffers when there are many originals. But it seems // safe to assume that in most cases there are few originals and many transformations for (std::vector::const_iterator o = originals.begin(); o != originals.end(); ++o) { // Extract the original shape and determine whether to cut or to fuse TopoDS_Shape shape; Part::TopoShape fuseShape; Part::TopoShape cutShape; if ((*o)->getTypeId().isDerivedFrom(PartDesign::FeatureAddSub::getClassTypeId())) { PartDesign::FeatureAddSub* feature = static_cast(*o); feature->getAddSubShape(fuseShape, cutShape); if (fuseShape.isNull() && cutShape.isNull()) return new App::DocumentObjectExecReturn("Shape of addsub feature is empty"); gp_Trsf trsf = feature->getLocation().Transformation().Multiplied(trsfInv); if (!fuseShape.isNull()) fuseShape = fuseShape.makETransform(trsf); if (!cutShape.isNull()) cutShape = cutShape.makETransform(trsf); } else { return new App::DocumentObjectExecReturn("Only additive and subtractive features can be transformed"); } TopoDS_Shape origShape = fuseShape.isNull()?cutShape.getShape():fuseShape.getShape(); TopoDS_Shape current = support; BRep_Builder builder; TopoDS_Compound compShape; builder.MakeCompound(compShape); std::vector shapes; bool overlapping = false; std::vector::const_iterator transformIter = transformations.begin(); // First transformation is skipped since it should not be part of the toolShape. transformIter++; for (; transformIter != transformations.end(); ++transformIter) { // Make an explicit copy of the shape because the "true" parameter to BRepBuilderAPI_Transform // seems to be pretty broken BRepBuilderAPI_Copy copy(origShape); shape = copy.Shape(); BRepBuilderAPI_Transform mkTrf(shape, *transformIter, false); // No need to copy, now if (!mkTrf.IsDone()) return new App::DocumentObjectExecReturn("Transformation failed", (*o)); shape = mkTrf.Shape(); shapes.emplace_back(shape); builder.Add(compShape, shape); if (overlapDetectionMode) overlapping = overlapping || (countSolids(TopoShape(origShape).fuse(shape))==1); } TopoDS_Shape toolShape; #ifndef FC_DEBUG if (overlapping || overlapMode == "Overlap mode") Base::Console().Message("Transformed: Overlapping feature mode (fusing tool shapes)\n"); else Base::Console().Message("Transformed: Non-Overlapping feature mode (compound of tool shapes)\n"); #endif if (overlapping || overlapMode == "Overlap mode") toolShape = TopoShape(shape).fuse(shapes, Precision::Confusion()); else toolShape = compShape; if (!fuseShape.isNull()) { std::unique_ptr mkBool(new BRepAlgoAPI_Fuse(current, toolShape)); if (!mkBool->IsDone()) { std::stringstream error; error << "Boolean operation failed"; return new App::DocumentObjectExecReturn(error.str()); } current = mkBool->Shape(); } else { std::unique_ptr mkBool(new BRepAlgoAPI_Cut(current, toolShape)); if (!mkBool->IsDone()) { std::stringstream error; error << "Boolean operation failed"; return new App::DocumentObjectExecReturn(error.str()); } current = mkBool->Shape(); } support = current; // Use result of this operation for fuse/cut of next original } support = refineShapeIfActive(support); int solidCount = countSolids(support); if (solidCount > 1) { Base::Console().Warning("Transformed: Result has multiple solids. Only keeping the first.\n"); } this->Shape.setValue(getSolid(support)); // picking the first solid rejected = getRemainingSolids(support); #ifndef FC_DEBUG Base::Console().Message("Transformed: Elapsed CPU time: %f s\n", (std::clock() - start0 ) / (double)(CLOCKS_PER_SEC)); #endif return App::DocumentObject::StdReturn; } TopoDS_Shape Transformed::refineShapeIfActive(const TopoDS_Shape& oldShape) const { if (this->Refine.getValue()) { try { Part::BRepBuilderAPI_RefineModel mkRefine(oldShape); TopoDS_Shape resShape = mkRefine.Shape(); if (!TopoShape(resShape).isClosed()) { return oldShape; } return resShape; } catch (Standard_Failure&) { return oldShape; } } return oldShape; } void Transformed::divideTools(const std::vector &toolsIn, std::vector &individualsOut, TopoDS_Compound &compoundOut) const { typedef std::pair ShapeBoundPair; typedef std::list PairList; typedef std::vector PairVector; PairList pairList; std::vector::const_iterator it; for (it = toolsIn.begin(); it != toolsIn.end(); ++it) { Bnd_Box bound; BRepBndLib::Add(*it, bound); bound.SetGap(0.0); ShapeBoundPair temp = std::make_pair(*it, bound); pairList.push_back(temp); } BRep_Builder builder; builder.MakeCompound(compoundOut); while(!pairList.empty()) { PairVector currentGroup; currentGroup.push_back(pairList.front()); pairList.pop_front(); PairList::iterator it = pairList.begin(); while(it != pairList.end()) { PairVector::const_iterator groupIt; bool found(false); for (groupIt = currentGroup.begin(); groupIt != currentGroup.end(); ++groupIt) { if (!(*it).second.IsOut((*groupIt).second)) {//touching means is out. found = true; break; } } if (found) { currentGroup.push_back(*it); pairList.erase(it); it=pairList.begin(); continue; } ++it; } if (currentGroup.size() == 1) { builder.Add(compoundOut, currentGroup.front().first); } else { PairVector::const_iterator groupIt; for (groupIt = currentGroup.begin(); groupIt != currentGroup.end(); ++groupIt) individualsOut.push_back((*groupIt).first); } } } TopoDS_Shape Transformed::getRemainingSolids(const TopoDS_Shape& shape) { BRep_Builder builder; TopoDS_Compound compShape; builder.MakeCompound(compShape); if (shape.IsNull()) Standard_Failure::Raise("Shape is null"); TopExp_Explorer xp; xp.Init(shape,TopAbs_SOLID); xp.Next(); // skip the first for (; xp.More(); xp.Next()) { builder.Add(compShape, xp.Current()); } return TopoDS_Shape(std::move(compShape)); } }