/*************************************************************************** * 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 #include #include #include #include #include #include "FeatureChamfer.h" #include using namespace PartDesign; PROPERTY_SOURCE(PartDesign::Chamfer, PartDesign::DressUp) const char* ChamferTypeEnums[] = {"Equal distance", "Two distances", "Distance and Angle", nullptr}; const App::PropertyQuantityConstraint::Constraints Chamfer::floatSize = {0.0, std::numeric_limits::max(), 0.1}; const App::PropertyAngle::Constraints Chamfer::floatAngle = {0.0, 180.0, 1.0}; static App::DocumentObjectExecReturn* validateParameters( int chamferType, double size, double size2, double angle ); Chamfer::Chamfer() { ADD_PROPERTY_TYPE(ChamferType, (0L), "Chamfer", App::Prop_None, "Type of chamfer"); ChamferType.setEnums(ChamferTypeEnums); ADD_PROPERTY_TYPE(Size, (1.0), "Chamfer", App::Prop_None, "Size of chamfer"); Size.setUnit(Base::Unit::Length); Size.setConstraints(&floatSize); ADD_PROPERTY_TYPE(Size2, (1.0), "Chamfer", App::Prop_None, "Second size of chamfer"); Size2.setUnit(Base::Unit::Length); Size2.setConstraints(&floatSize); ADD_PROPERTY_TYPE(Angle, (45.0), "Chamfer", App::Prop_None, "Angle of chamfer"); Angle.setUnit(Base::Unit::Angle); Angle.setConstraints(&floatAngle); ADD_PROPERTY_TYPE(FlipDirection, (false), "Chamfer", App::Prop_None, "Flip direction"); ADD_PROPERTY_TYPE( UseAllEdges, (false), "Chamfer", App::Prop_None, "Chamfer all edges if true, else use only those edges in Base property.\n" "If true, then this overrides any edge changes made to the Base property or in the " "dialog.\n" ); updateProperties(); } short Chamfer::mustExecute() const { bool touched = false; auto chamferType = ChamferType.getValue(); switch (chamferType) { case 0: // "Equal distance" touched = Size.isTouched() || ChamferType.isTouched(); break; case 1: // "Two distances" touched = Size.isTouched() || ChamferType.isTouched() || Size2.isTouched(); break; case 2: // "Distance and Angle" touched = Size.isTouched() || ChamferType.isTouched() || Angle.isTouched(); break; } if (Placement.isTouched() || touched) { return 1; } return DressUp::mustExecute(); } App::DocumentObjectExecReturn* Chamfer::execute() { if (onlyHaveRefined()) { return App::DocumentObject::StdReturn; } // NOTE: Normally the Base property and the BaseFeature property should point to the same object. // The only difference is that the Base property also stores the edges that are to be chamfered Part::TopoShape TopShape; try { TopShape = getBaseTopoShape(); } catch (Base::Exception& e) { return new App::DocumentObjectExecReturn(e.what()); } TopShape.setTransform(Base::Matrix4D()); auto edges = UseAllEdges.getValue() ? TopShape.getSubTopoShapes(TopAbs_EDGE) : getContinuousEdges(TopShape); if (edges.empty()) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP("Exception", "No edges specified")); } const int chamferType = ChamferType.getValue(); const double size = Size.getValue(); double size2 = Size2.getValue(); const double angle = Angle.getValue(); const bool flipDirection = FlipDirection.getValue(); auto res = validateParameters(chamferType, size, size2, angle); if (res != App::DocumentObject::StdReturn) { return res; } this->positionByBaseFeature(); if (static_cast(chamferType) == Part::ChamferType::distanceAngle) { size2 = angle; } try { TopoShape shape(0); shape.makeElementChamfer( TopShape, edges, static_cast(chamferType), size, size2, nullptr, flipDirection ? Part::Flip::flip : Part::Flip::none ); if (shape.isNull()) { return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Failed to create chamfer") ); } TopTools_ListOfShape aLarg; aLarg.Append(TopShape.getShape()); if (!BRepAlgo::IsValid(aLarg, shape.getShape(), Standard_False, Standard_False)) { ShapeFix_ShapeTolerance aSFT; aSFT.LimitTolerance( shape.getShape(), Precision::Confusion(), Precision::Confusion(), TopAbs_SHAPE ); } // store shape before refinement this->rawShape = shape; shape = refineShapeIfActive(shape); if (!isSingleSolidRuleSatisfied(shape.getShape())) { return new App::DocumentObjectExecReturn(QT_TRANSLATE_NOOP( "Exception", "Result has multiple solids: enable 'Allow Compound' in the active body." )); } shape = getSolid(shape); this->Shape.setValue(shape); return App::DocumentObject::StdReturn; } catch (Standard_Failure& e) { return new App::DocumentObjectExecReturn(e.GetMessageString()); } } void Chamfer::Restore(Base::XMLReader& reader) { DressUp::Restore(reader); migrateFlippedProperties(reader); } void Chamfer::handleChangedPropertyType(Base::XMLReader& reader, const char* TypeName, App::Property* prop) { if (prop && strcmp(TypeName, "App::PropertyFloatConstraint") == 0 && strcmp(prop->getTypeId().getName(), "App::PropertyQuantityConstraint") == 0) { App::PropertyFloatConstraint p; p.Restore(reader); static_cast(prop)->setValue(p.getValue()); } else { DressUp::handleChangedPropertyType(reader, TypeName, prop); } } bool Chamfer::requiresSizeSwapping(const Base::XMLReader& reader) const { return Base::getVersion(reader.ProgramVersion) < Base::Version::v1_0 && (ChamferType.getValue() == 1 || ChamferType.getValue() == 2); } void Chamfer::migrateFlippedProperties(const Base::XMLReader& reader) { if (!requiresSizeSwapping(reader)) { return; } Base::Console().warning( "The 'FlipDirection' property of the chamfer of %s is being adjusted to maintain" "the same geometry in this FreeCAD version. If the re-saved file is later opened " "in FreeCAD 0.21.x the chamfer result may differ due to the changed parameter " "interpretation.\n", getFullName() ); FlipDirection.setValue(!FlipDirection.getValue()); } void Chamfer::onChanged(const App::Property* prop) { if (prop == &ChamferType) { updateProperties(); } DressUp::onChanged(prop); } void Chamfer::updateProperties() { auto chamferType = ChamferType.getValue(); auto disableproperty = [](App::Property* prop, bool on) { prop->setStatus(App::Property::ReadOnly, on); }; switch (chamferType) { case 0: // "Equal distance" disableproperty(&this->Angle, true); disableproperty(&this->Size2, true); break; case 1: // "Two distances" disableproperty(&this->Angle, true); disableproperty(&this->Size2, false); break; case 2: // "Distance and Angle" disableproperty(&this->Angle, false); disableproperty(&this->Size2, true); break; } } static App::DocumentObjectExecReturn* validateParameters( int chamferType, double size, double size2, double angle ) { // Size is common to all chamfer types. if (size <= 0) { return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Size must be greater than zero") ); } switch (chamferType) { case 0: // Equal distance // Nothing to do. break; case 1: // Two distances if (size2 <= 0) { return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Size2 must be greater than zero") ); } break; case 2: // Distance and angle if (angle <= 0 || angle >= 180.0) { return new App::DocumentObjectExecReturn( QT_TRANSLATE_NOOP("Exception", "Angle must be greater than 0 and less than 180") ); } break; } return App::DocumentObject::StdReturn; }