Files
create/src/Mod/Part/App/FeatureMirroring.cpp

272 lines
12 KiB
C++

/***************************************************************************
* Copyright (c) 2010 Werner Mayer <wmayer[at]users.sourceforge.net> *
* *
* 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 <BRepBuilderAPI_Transform.hxx>
# include <BRep_Tool.hxx>
# include <gp_Ax2.hxx>
# include <gp_Circ.hxx>
# include <gp_Dir.hxx>
# include <gp_Pnt.hxx>
# include <gp_Trsf.hxx>
# include <BRepAdaptor_Curve.hxx>
# include <BRepAdaptor_Surface.hxx>
# include <gp_Pln.hxx>
# include <Geom_Plane.hxx>
# include <TopoDS.hxx>
# include <TopoDS_Face.hxx>
# include <TopExp_Explorer.hxx>
#endif
#include <Mod/Part/App/PrimitiveFeature.h>
#include <App/Link.h>
#include <App/OriginFeature.h>
#include "FeatureMirroring.h"
#include "DatumFeature.h"
using namespace Part;
PROPERTY_SOURCE(Part::Mirroring, Part::Feature)
Mirroring::Mirroring()
{
ADD_PROPERTY(Source,(nullptr));
ADD_PROPERTY_TYPE(Base,(Base::Vector3d()),"Plane",App::Prop_None,"The base point of the plane");
ADD_PROPERTY_TYPE(Normal,(Base::Vector3d(0,0,1)),"Plane",App::Prop_None,"The normal of the plane");
ADD_PROPERTY_TYPE(MirrorPlane,(nullptr),"Plane",App::Prop_None,"A reference for the mirroring plane, overrides Base and Normal if set, can be face or circle");
}
short Mirroring::mustExecute() const
{
if (Source.isTouched())
return 1;
if (Base.isTouched())
return 1;
if (Normal.isTouched())
return 1;
if (MirrorPlane.isTouched())
return 1;
return 0;
}
void Mirroring::onChanged(const App::Property* prop)
{
/**
In the case the user has a reference plane object, then
Base and Normal are computed based on that object. We must
handle this by setting the changes to Base and Normal to not
trigger a recompute when they are computed and changed. We
should also set Base and Normal to readonly so not to confuse
the user, who might try to change them despite having a reference
object. We could also hide them, but they contain useful information.
*/
if (!isRestoring()) {
bool needsRecompute = false;
App::DocumentObject* refObject = MirrorPlane.getValue();
if (!refObject){
Base.setStatus(App::Property::ReadOnly, false);
Normal.setStatus(App::Property::ReadOnly, false);
if (prop == &Base || prop == &Normal) {
needsRecompute = true;
}
} else {
if (prop == &MirrorPlane){
Base.setStatus(App::Property::ReadOnly, true);
Normal.setStatus(App::Property::ReadOnly, true);
needsRecompute = true;
}
}
if (needsRecompute){
try {
App::DocumentObjectExecReturn *ret = recompute();
delete ret;
}
catch (...) {
}
}
}
Part::Feature::onChanged(prop);
}
void Mirroring::handleChangedPropertyType(Base::XMLReader &reader, const char *TypeName, App::Property *prop)
{
if (prop == &Base && strcmp(TypeName, "App::PropertyVector") == 0) {
App::PropertyVector v;
v.Restore(reader);
Base.setValue(v.getValue());
}
else if (prop == &Normal && strcmp(TypeName, "App::PropertyVector") == 0) {
App::PropertyVector v;
v.Restore(reader);
Normal.setValue(v.getValue());
}
else {
Part::Feature::handleChangedPropertyType(reader, TypeName, prop);
}
}
App::DocumentObjectExecReturn *Mirroring::execute()
{
App::DocumentObject* link = Source.getValue();
if (!link)
return new App::DocumentObjectExecReturn("No object linked");
App::DocumentObject* refObject = MirrorPlane.getValue();
std::vector<std::string> subStrings = MirrorPlane.getSubValues();
gp_Pnt axbase;
gp_Dir axdir;
/**
Support mirror plane reference objects:
DatumPlanes, Part::Planes, Origin planes, Faces, Circles
Can also be App::Links to such objects
*/
if (refObject){
if (refObject->isDerivedFrom(Part::Plane::getClassTypeId()) || refObject->isDerivedFrom<App::Plane>() || (strstr(refObject->getNameInDocument(), "Plane")
&& refObject->isDerivedFrom(Part::Datum::getClassTypeId()))) {
Part::Feature* plane = static_cast<Part::Feature*>(refObject);
Base::Vector3d base = plane->Placement.getValue().getPosition();
axbase = gp_Pnt(base.x, base.y, base.z);
Base::Rotation rot = plane->Placement.getValue().getRotation();
Base::Vector3d dir;
rot.multVec(Base::Vector3d(0,0,1), dir);
axdir = gp_Dir(dir.x, dir.y, dir.z);
// reference is an app::link or a part::feature or some subobject
} else if (refObject->isDerivedFrom<Part::Feature>() || refObject->isDerivedFrom<App::Link>()) {
if (subStrings.size() > 1){
throw Base::ValueError(std::string(this->getFullLabel()) + ": Only 1 subobject is supported for Mirror Plane reference, either a plane face or a circle edge.");
}
auto linked = MirrorPlane.getValue();
bool isFace = false; //will be true if user selected face subobject or if object only has 1 face
bool isEdge = false; //will be true if user selected edge subobject or if object only has 1 edge
TopoDS_Shape shape;
if (!subStrings.empty() && subStrings[0].length() > 0){
shape = Feature::getTopoShape(linked, subStrings[0].c_str(), true).getShape();
if (strstr(subStrings[0].c_str(), "Face")){
isFace = true; //was face subobject, e.g. Face3
} else {
if (strstr(subStrings[0].c_str(), "Edge")){
isEdge = true; //was edge subobject, e.g. Edge7
}
}
} else {
shape = Feature::getShape(linked); //no subobjects were selected, so this is entire shape of feature
}
// if there is only 1 face or 1 edge, then we don't need to force the user to select that face or edge
// instead we can infer what was intended
int faceCount = Part::TopoShape(shape).countSubShapes(TopAbs_FACE);
int edgeCount = Part::TopoShape(shape).countSubShapes(TopAbs_EDGE);
TopoDS_Face face;
TopoDS_Edge edge;
if (isFace) { //user selected a face, so use shape to get the TopoDS::Face
face = TopoDS::Face(shape);
} else {
if (faceCount == 1) { //entire feature selected, but it only has 1 face, so get that face
TopoDS_Shape tdface = Part::TopoShape(shape).getSubShape(std::string("Face1").c_str());
face = TopoDS::Face(tdface);
isFace = true;
}
}
if (!isFace && isEdge){ //don't bother with edge if we already have a face to work with
edge = TopoDS::Edge(shape); //isEdge means an edge was selected
} else {
if (edgeCount == 1){ //we don't have a face yet and there were no edges in the subobject selection
//but since this object only has 1 edge, we use it
TopoDS_Shape tdedge = Part::TopoShape(shape).getSubShape(std::string("Edge1").c_str());
edge = TopoDS::Edge(tdedge);
isEdge = true;
}
}
if (isFace && face.IsNull()) { //ensure we have a good face to work with
throw Base::ValueError(std::string(this->getFullLabel()) + ": Failed to extract mirror plane because face is null");
}
if (isEdge && edge.IsNull()){ //ensure we have a good edge to work with
throw Base::ValueError(std::string(this->getFullLabel()) + ": Failed to extract mirror plane because edge is null");
}
if (!isFace && !isEdge){
throw Base::ValueError(std::string(this->getFullLabel()) + ": Failed to extract mirror plane, unable to determine which face or edge to use.");
}
if (isFace) {
BRepAdaptor_Surface adapt(face);
if (adapt.GetType() != GeomAbs_Plane)
throw Base::TypeError(std::string(this->getFullLabel()) + ": Mirror plane face must be planar");
TopExp_Explorer exp;
exp.Init(face, TopAbs_VERTEX);
if (exp.More()) {
axbase = BRep_Tool::Pnt(TopoDS::Vertex(exp.Current()));
}
axdir = adapt.Plane().Axis().Direction();
} else {
if (isEdge){
BRepAdaptor_Curve curve(edge);
if (!(curve.GetType() == GeomAbs_Circle)) {
throw Base::TypeError(std::string(this->getFullLabel()) + ": Only circle edge types are supported");
}
gp_Circ circle = curve.Circle();
axdir = circle.Axis().Direction();
axbase = circle.Location();
}
}
} else {
throw Base::ValueError(std::string(this->getFullLabel()) + ": Mirror plane reference must be a face of a feature or a plane object or a circle");
}
Base.setValue(axbase.X(), axbase.Y(), axbase.Z());
Normal.setValue(axdir.X(), axdir.Y(), axdir.Z());
}
Base::Vector3d base = Base.getValue();
Base::Vector3d norm = Normal.getValue();
try {
const TopoDS_Shape& shape = Feature::getShape(link);
if (shape.IsNull())
Standard_Failure::Raise(std::string(std::string(this->getFullLabel()) + ": Cannot mirror empty shape").c_str());
gp_Ax2 ax2(gp_Pnt(base.x,base.y,base.z), gp_Dir(norm.x,norm.y,norm.z));
gp_Trsf mat;
mat.SetMirror(ax2);
TopLoc_Location loc = shape.Location();
gp_Trsf placement = loc.Transformation();
mat = placement * mat;
BRepBuilderAPI_Transform mkTrf(shape, mat);
this->Shape.setValue(mkTrf.Shape());
return App::DocumentObject::StdReturn;
}
catch (Standard_Failure& e) {
return new App::DocumentObjectExecReturn(e.GetMessageString());
}
}