/****************************************************************************** * Copyright (c)2012 Jan Rheinlaender * * * * 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 #endif #include "FeatureTransformed.h" #include "FeatureMultiTransform.h" #include "FeatureAdditive.h" #include "FeatureSubtractive.h" #include "FeatureMirrored.h" #include "FeatureLinearPattern.h" #include "FeaturePolarPattern.h" #include #include #include #include #include #include using namespace PartDesign; namespace PartDesign { PROPERTY_SOURCE(PartDesign::Transformed, PartDesign::Feature) Transformed::Transformed() { ADD_PROPERTY(Originals,(0)); Originals.setSize(0); Placement.setStatus(App::Property::ReadOnly, true); } void Transformed::positionBySupport(void) { Part::Feature *support = static_cast(getSupportObject()); if ((support != NULL) && support->getTypeId().isDerivedFrom(Part::Feature::getClassTypeId())) this->Placement.setValue(support->Placement.getValue()); } App::DocumentObject* Transformed::getSupportObject() const { if (BaseFeature.getValue() != NULL) return BaseFeature.getValue(); else { if (!Originals.getValues().empty()) return Originals.getValues().front(); // For legacy features else return NULL; } } App::DocumentObject* Transformed::getSketchObject() const { std::vector originals = Originals.getValues(); if (!originals.empty() && originals.front()->getTypeId().isDerivedFrom(PartDesign::SketchBased::getClassTypeId())) { return (static_cast(originals.front()))->getVerifiedSketch(); } 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) { reader.readElement("Properties"); int Cnt = reader.getAttributeAsInteger("Count"); for (int i=0 ;igetTypeId().getName(), TypeName) == 0) { prop->Restore(reader); } else if (prop) { 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 implmentation // has changed. So, create a temporary PropertyFloat object and assign the value. App::PropertyFloat floatProp; floatProp.Restore(reader); static_cast(prop)->setValue(floatProp.getValue()); } } } catch (const Base::XMLParseException&) { throw; // re-throw } catch (const Base::Exception &e) { Base::Console().Error("%s\n", e.what()); } catch (const std::exception &e) { Base::Console().Error("%s\n", e.what()); } catch (const char* e) { Base::Console().Error("%s\n", e); } #ifndef FC_DEBUG catch (...) { Base::Console().Error("Primitive::Restore: Unknown C++ exception thrown"); } #endif reader.readEndElement("Property"); } reader.readEndElement("Properties"); } short Transformed::mustExecute() const { if (Originals.isTouched()) return 1; return PartDesign::Feature::mustExecute(); } App::DocumentObjectExecReturn *Transformed::execute(void) { rejected.clear(); std::vector originals = Originals.getValues(); if (originals.empty()) // typically InsideMultiTransform return App::DocumentObject::StdReturn; 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 = static_cast(getSupportObject()); if (supportFeature == NULL) return new App::DocumentObjectExecReturn("No support for transformation feature"); const Part::TopoShape& supportTopShape = supportFeature->Shape.getShape(); if (supportTopShape._Shape.IsNull()) return new App::DocumentObjectExecReturn("Cannot transform invalid support shape"); // create an untransformed copy of the support shape Part::TopoShape supportShape(supportTopShape); supportShape.setTransform(Base::Matrix4D()); TopoDS_Shape support = supportShape._Shape; typedef std::set::const_iterator> trsf_it; typedef std::map rej_it_map; rej_it_map nointersect_trsfms; rej_it_map overlapping_trsfms; // 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; bool fuse; if ((*o)->getTypeId().isDerivedFrom(PartDesign::Additive::getClassTypeId())) { PartDesign::Additive* addFeature = static_cast(*o); shape = addFeature->AddShape.getShape()._Shape; if (shape.IsNull()) return new App::DocumentObjectExecReturn("Shape of additive feature is empty"); fuse = true; } else if ((*o)->getTypeId().isDerivedFrom(PartDesign::Subtractive::getClassTypeId())) { PartDesign::Subtractive* subFeature = static_cast(*o); shape = subFeature->SubShape.getShape()._Shape; if (shape.IsNull()) return new App::DocumentObjectExecReturn("Shape of subtractive feature is empty"); fuse = false; } else { return new App::DocumentObjectExecReturn("Only additive and subtractive features can be transformed"); } // Transform the add/subshape and collect the resulting shapes for overlap testing typedef std::vector::const_iterator> trsf_it_vec; trsf_it_vec v_transformations; std::vector v_transformedShapes; std::vector::const_iterator t = transformations.begin(); ++t; // Skip first transformation, which is always the identity transformation for (; t != transformations.end(); ++t) { // Make an explicit copy of the shape because the "true" parameter to BRepBuilderAPI_Transform // seems to be pretty broken BRepBuilderAPI_Copy copy(shape); shape = copy.Shape(); if (shape.IsNull()) return new App::DocumentObjectExecReturn("Transformed: Linked shape object is empty"); BRepBuilderAPI_Transform mkTrf(shape, *t, false); // No need to copy, now if (!mkTrf.IsDone()) return new App::DocumentObjectExecReturn("Transformation failed", (*o)); // Check for intersection with support try { if (!Part::checkIntersection(support, mkTrf.Shape(), false, true)) { #ifdef FC_DEBUG // do not write this in release mode because a message appears already in the task view Base::Console().Warning("Transformed shape does not intersect support %s: Removed\n", (*o)->getNameInDocument()); #endif nointersect_trsfms[*o].insert(t); } else { v_transformations.push_back(t); v_transformedShapes.push_back(mkTrf.Shape()); // Note: Transformations that do not intersect the support are ignored in the overlap tests } } catch (Standard_Failure) { // Note: Ignoring this failure is probably pointless because if the intersection check fails, the later // fuse operation of the transformation result will also fail Handle_Standard_Failure e = Standard_Failure::Caught(); std::string msg("Transformation: Intersection check failed"); if (e->GetMessageString() != NULL) msg += std::string(": '") + e->GetMessageString() + "'"; return new App::DocumentObjectExecReturn(msg.c_str()); } } if (v_transformedShapes.empty()) continue; // Skip the overlap check and go on to next original // Check for overlapping of the original and the transformed shapes, and remove the overlapping transformations if (this->getTypeId() != PartDesign::MultiTransform::getClassTypeId()) { // If there is only one transformed feature, we allow an overlap (though it might seem // illogical to the user why we allow overlapping shapes in this case!) if (v_transformedShapes.size() > 1) if (Part::checkIntersection(shape, v_transformedShapes.front(), false, false)) { // For single transformations, if one overlaps, all overlap, as long as we have uniform increments for (trsf_it_vec::const_iterator v = v_transformations.begin(); v != v_transformations.end(); v++) overlapping_trsfms[*o].insert(*v); v_transformedShapes.clear(); } } else { // For MultiTransform, just checking the first transformed shape is not sufficient - any two // features might overlap, even if the original and the first shape don't overlap! typedef std::set::iterator> shape_it_set; shape_it_set rejected_iterators; std::vector::iterator s1 = v_transformedShapes.begin(); std::vector::iterator s2 = s1; ++s2; trsf_it_vec::const_iterator t1 = v_transformations.begin(); trsf_it_vec::const_iterator t2 = t1; ++t2; for (; s2 != v_transformedShapes.end();) { // Check intersection with the original if (Part::checkIntersection(shape, *s1, false, false)) { rejected_iterators.insert(s1); overlapping_trsfms[*o].insert(*t1); } // Check intersection with other transformations for (; s2 != v_transformedShapes.end(); ++s2, ++t2) if (Part::checkIntersection(*s1, *s2, false, false)) { rejected_iterators.insert(s1); rejected_iterators.insert(s2); overlapping_trsfms[*o].insert(*t1); overlapping_trsfms[*o].insert(*t2); } ++s1; s2 = s1; ++s2; ++t1; t2 = t1; ++t2; } // Check intersection of last transformation with the original if (Part::checkIntersection(shape, *s1, false, false)) { rejected_iterators.insert(s1); overlapping_trsfms[*o].insert(*t1); } for (shape_it_set::reverse_iterator it = rejected_iterators.rbegin(); it != rejected_iterators.rend(); ++it) v_transformedShapes.erase(*it); } if (v_transformedShapes.empty()) continue; // Skip the boolean operation and go on to next original // Build a compound from all the valid transformations BRep_Builder builder; TopoDS_Compound transformedShapes; builder.MakeCompound(transformedShapes); for (std::vector::const_iterator s = v_transformedShapes.begin(); s != v_transformedShapes.end(); ++s) builder.Add(transformedShapes, *s); // Fuse/Cut the compounded transformed shapes with the support TopoDS_Shape result; if (fuse) { BRepAlgoAPI_Fuse mkFuse(support, transformedShapes); if (!mkFuse.IsDone()) return new App::DocumentObjectExecReturn("Fusion with support failed", *o); // we have to get the solids (fuse sometimes creates compounds) result = this->getSolid(mkFuse.Shape()); // lets check if the result is a solid if (result.IsNull()) return new App::DocumentObjectExecReturn("Resulting shape is not a solid", *o); result = refineShapeIfActive(result); } else { BRepAlgoAPI_Cut mkCut(support, transformedShapes); if (!mkCut.IsDone()) return new App::DocumentObjectExecReturn("Cut out of support failed", *o); result = mkCut.Shape(); result = refineShapeIfActive(result); } support = result; // Use result of this operation for fuse/cut of next original } if (!overlapping_trsfms.empty()) // Concentrate on overlapping shapes since they are more serious for (rej_it_map::const_iterator it = overlapping_trsfms.begin(); it != overlapping_trsfms.end(); ++it) for (trsf_it::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) rejected[it->first].push_back(**it2); else for (rej_it_map::const_iterator it = nointersect_trsfms.begin(); it != nointersect_trsfms.end(); ++it) for (trsf_it::const_iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2) rejected[it->first].push_back(**it2); this->Shape.setValue(support); if (!overlapping_trsfms.empty()) return new App::DocumentObjectExecReturn("Transformed objects are overlapping, try using a higher length or reducing the number of occurrences"); // Note: This limitation could be overcome by fusing the transformed features instead of // compounding them, probably at the expense of quite a bit of performance and complexity // in this code else return App::DocumentObject::StdReturn; } TopoDS_Shape Transformed::refineShapeIfActive(const TopoDS_Shape& oldShape) const { Base::Reference hGrp = App::GetApplication().GetUserParameter() .GetGroup("BaseApp")->GetGroup("Preferences")->GetGroup("Mod/PartDesign"); if (hGrp->GetBool("RefineModel", false)) { Part::BRepBuilderAPI_RefineModel mkRefine(oldShape); TopoDS_Shape resShape = mkRefine.Shape(); return resShape; } return oldShape; } }