/*************************************************************************** * Copyright (c) 2016 WandererFan * * * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #endif #include #include #include #include #include "DrawComplexSection.h" #include "DrawUtil.h" #include "DrawViewDetail.h" #include "DrawViewSection.h" #include "GeometryObject.h" #include "Preferences.h" #include "ShapeUtils.h" using namespace TechDraw; //=========================================================================== // DrawViewDetail //=========================================================================== PROPERTY_SOURCE(TechDraw::DrawViewDetail, TechDraw::DrawViewPart) DrawViewDetail::DrawViewDetail() : m_waitingForDetail(false), m_saveDvp(nullptr), m_saveDvs(nullptr) { static const char* dgroup = "Detail"; ADD_PROPERTY_TYPE(BaseView, (nullptr), dgroup, App::Prop_None, "2D View source for this Section"); BaseView.setScope(App::LinkScope::Global); ADD_PROPERTY_TYPE(AnchorPoint, (0, 0, 0), dgroup, App::Prop_None, "Location of detail in BaseView"); ADD_PROPERTY_TYPE(Radius, (10.0), dgroup, App::Prop_None, "Size of detail area"); ADD_PROPERTY_TYPE(Reference, ("1"), dgroup, App::Prop_None, "An identifier for this detail"); static const char* agroup{"Appearance"}; ADD_PROPERTY_TYPE(ShowMatting, (Preferences::showDetailMatting()), agroup, App::Prop_None, "Show or hide the matting around the detail view"); ADD_PROPERTY_TYPE(ShowHighlight, (Preferences::showDetailHighlight()), agroup, App::Prop_None, "Show or hide the detail highlight in the source view"); getParameters(); m_fudge = 1.01; //hide Properties not relevant to DVDetail Direction.setStatus(App::Property::ReadOnly, true);//Should be same as BaseView Rotation.setStatus(App::Property::ReadOnly, true); //same as BaseView ScaleType.setValue("Custom"); //dvd uses scale from BaseView } DrawViewDetail::~DrawViewDetail() { //don't delete this object while it still has dependent tasks running if (m_detailFuture.isRunning()) { Base::Console().Message("%s is waiting for detail cut to finish\n", Label.getValue()); m_detailFuture.waitForFinished(); } } short DrawViewDetail::mustExecute() const { if (isRestoring()) { TechDraw::DrawView::mustExecute(); } if (AnchorPoint.isTouched() || Radius.isTouched() || BaseView.isTouched()) { return 1; } return TechDraw::DrawView::mustExecute(); } void DrawViewDetail::onChanged(const App::Property* prop) { if (isRestoring()) { DrawView::onChanged(prop); return; } if (prop == &Reference) { std::string lblText = "Detail " + std::string(Reference.getValue()); Label.setValue(lblText); } DrawViewPart::onChanged(prop); } App::DocumentObjectExecReturn* DrawViewDetail::execute() { // Base::Console().Message("DVD::execute() - %s\n", getNameInDocument()); if (!keepUpdated()) { return DrawView::execute(); } App::DocumentObject* baseObj = BaseView.getValue(); if (!baseObj) { return DrawView::execute(); } if (!baseObj->isDerivedFrom()) { //this can only happen via scripting? return DrawView::execute(); } DrawViewPart* dvp = static_cast(baseObj); TopoDS_Shape shape = dvp->getShapeForDetail(); DrawViewSection* dvs = nullptr; if (dvp->isDerivedFrom(TechDraw::DrawViewSection::getClassTypeId())) { dvs = static_cast(dvp); } if (shape.IsNull()) { return DrawView::execute(); } bool haveX = checkXDirection(); if (!haveX) { //block touch/onChanged stuff Base::Vector3d newX = getXDirection(); XDirection.setValue(newX); XDirection.purgeTouched();//don't trigger updates! //unblock } detailExec(shape, dvp, dvs); addPoints(); dvp->requestPaint();//to refresh detail highlight in base view return DrawView::execute(); } //try to create a detail of the solids & shells in shape //if there are no solids/shells in shape, use the edges in shape void DrawViewDetail::detailExec(TopoDS_Shape& shape, DrawViewPart* dvp, DrawViewSection* dvs) { if (waitingForHlr() || waitingForDetail()) { return; } //note that &m_detailWatcher in the third parameter is not strictly required, but using the //4 parameter signature instead of the 3 parameter signature prevents clazy warning: //https://github.com/KDE/clazy/blob/1.11/docs/checks/README-connect-3arg-lambda.md connectDetailWatcher = QObject::connect(&m_detailWatcher, &QFutureWatcherBase::finished, &m_detailWatcher, [this] { this->onMakeDetailFinished(); }); // We create a lambda closure to hold a copy of shape. // This is important because this variable might be local to the calling // function and might get destructed before the parallel processing finishes. // TODO: What about dvp and dvs? Do they live past makeDetailShape? auto lambda = [this, shape, dvp, dvs]{this->makeDetailShape(shape, dvp, dvs);}; m_detailFuture = QtConcurrent::run(std::move(lambda)); m_detailWatcher.setFuture(m_detailFuture); waitingForDetail(true); } //this runs in a separate thread since it can sometimes take a long time //make a common of the input shape and a cylinder (or prism depending on //the matting style) void DrawViewDetail::makeDetailShape(const TopoDS_Shape& shape, DrawViewPart* dvp, DrawViewSection* dvs) { showProgressMessage(getNameInDocument(), "is making detail shape"); Base::Vector3d dirDetail = dvp->Direction.getValue(); double radius = getFudgeRadius(); //make a copy of the input shape so we don't inadvertently change it BRepBuilderAPI_Copy BuilderCopy(shape); TopoDS_Shape copyShape = BuilderCopy.Shape(); m_saveShape = copyShape; m_saveDvp = dvp; m_saveDvs = dvs; gp_Pnt gpCenter = ShapeUtils::findCentroid(copyShape, dirDetail); Base::Vector3d shapeCenter = Base::Vector3d(gpCenter.X(), gpCenter.Y(), gpCenter.Z()); m_saveCentroid = shapeCenter;//centroid of original shape if (!dvs) { //section cutShape should already be on origin copyShape = ShapeUtils::moveShape(copyShape,//centre shape on origin -shapeCenter); } shapeCenter = Base::Vector3d(0.0, 0.0, 0.0); m_viewAxis = dvp->getProjectionCS(shapeCenter);//save the CS for later Base::Vector3d anchor = AnchorPoint.getValue();//this is a 2D point in base view local coords anchor = DrawUtil::toR3(m_viewAxis, anchor);//actual anchor coords in R3 Bnd_Box bbxSource; bbxSource.SetGap(0.0); BRepBndLib::AddOptimal(copyShape, bbxSource); double diag = sqrt(bbxSource.SquareExtent()); Base::Vector3d toolPlaneOrigin = anchor + dirDetail * diag * -1.0;//center tool about anchor double extrudeLength = 2.0 * toolPlaneOrigin.Length(); gp_Pnt gpnt(toolPlaneOrigin.x, toolPlaneOrigin.y, toolPlaneOrigin.z); gp_Dir gdir(dirDetail.x, dirDetail.y, dirDetail.z); TopoDS_Face extrusionFace; Base::Vector3d extrudeVec = dirDetail * extrudeLength; gp_Vec extrudeDir(extrudeVec.x, extrudeVec.y, extrudeVec.z); TopoDS_Shape tool; if (Preferences::mattingStyle()) { //square mat gp_Pln gpln(gpnt, gdir); BRepBuilderAPI_MakeFace mkFace(gpln, -radius, radius, -radius, radius); extrusionFace = mkFace.Face(); if (extrusionFace.IsNull()) { Base::Console().Warning("DVD::makeDetailShape - %s - failed to create tool base face\n", getNameInDocument()); return; } tool = BRepPrimAPI_MakePrism(extrusionFace, extrudeDir, false, true).Shape(); if (tool.IsNull()) { Base::Console().Warning("DVD::makeDetailShape - %s - failed to create tool (prism)\n", getNameInDocument()); return; } } else { //circular mat gp_Ax2 cs(gpnt, gdir); BRepPrimAPI_MakeCylinder mkTool(cs, radius, extrudeLength); tool = mkTool.Shape(); if (tool.IsNull()) { Base::Console().Warning("DVD::detailExec - %s - failed to create tool (cylinder)\n", getNameInDocument()); return; } } BRepAlgoAPI_Common mkCommon(copyShape, tool); if (!mkCommon.IsDone() || mkCommon.Shape().IsNull()) { Base::Console().Warning("DVD::detailExec - %s - failed to create detail shape\n", getNameInDocument()); return; } // save the detail shape for further processing m_detailShape = mkCommon.Shape(); if (debugDetail()) { BRepTools::Write(tool, "DVDTool.brep"); //debug BRepTools::Write(copyShape, "DVDCopy.brep");//debug BRepTools::Write(m_detailShape, "DVDCommon.brep"); //debug } gp_Pnt inputCenter = ShapeUtils::findCentroid(m_detailShape, dirDetail); Base::Vector3d centroid(inputCenter.X(), inputCenter.Y(), inputCenter.Z()); m_saveCentroid += centroid;//center of massaged shape //align shape with detail anchor TopoDS_Shape centeredShape = ShapeUtils::moveShape(m_detailShape, anchor * -1.0); m_scaledShape = ShapeUtils::scaleShape(centeredShape, getScale()); if (debugDetail()) { BRepTools::Write(m_scaledShape, "DVDScaled.brep");//debug } Base::Vector3d stdOrg(0.0, 0.0, 0.0); m_viewAxis = dvp->getProjectionCS(stdOrg); if (!DrawUtil::fpCompare(Rotation.getValue(), 0.0)) { m_scaledShape = ShapeUtils::rotateShape(m_scaledShape, m_viewAxis, Rotation.getValue()); } showProgressMessage(getNameInDocument(), "has finished making detail shape"); } void DrawViewDetail::postHlrTasks(void) { // Base::Console().Message("DVD::postHlrTasks()\n"); DrawViewPart::postHlrTasks(); geometryObject->pruneVertexGeom(Base::Vector3d(0.0, 0.0, 0.0), Radius.getValue() * getScale());//remove vertices beyond clipradius //second pass if required if (ScaleType.isValue("Automatic") && !checkFit()) { double newScale = autoScale(); Scale.setValue(newScale); Scale.purgeTouched(); detailExec(m_saveShape, m_saveDvp, m_saveDvs); } overrideKeepUpdated(false); } //continue processing after makeDetailShape thread is finished void DrawViewDetail::onMakeDetailFinished(void) { waitingForDetail(false); QObject::disconnect(connectDetailWatcher); //ancestor's buildGeometryObject will run HLR and face finding in a separate thread m_tempGeometryObject = buildGeometryObject(m_scaledShape, m_viewAxis); } bool DrawViewDetail::waitingForResult() const { if (DrawViewPart::waitingForResult() || waitingForDetail()) { return true; } return false; } TopoDS_Shape DrawViewDetail::projectEdgesOntoFace(TopoDS_Shape& edgeShape, TopoDS_Face& projFace, gp_Dir& projDir) { BRep_Builder builder; TopoDS_Compound edges; builder.MakeCompound(edges); TopExp_Explorer Ex(edgeShape, TopAbs_EDGE); while (Ex.More()) { TopoDS_Edge e = TopoDS::Edge(Ex.Current()); BRepProj_Projection mkProj(e, projFace, projDir); if (mkProj.IsDone()) { builder.Add(edges, mkProj.Shape()); } Ex.Next(); } if (debugDetail()) { BRepTools::Write(edges, "DVDEdges.brep");//debug } return TopoDS_Shape(std::move(edges)); } //we don't want to paint detail highlights on top of detail views, //so tell the Gui that there are no details for this view std::vector DrawViewDetail::getDetailRefs() const { return std::vector(); } double DrawViewDetail::getFudgeRadius() { return Radius.getValue() * m_fudge; } bool DrawViewDetail::debugDetail() const { return Preferences::getPreferenceGroup("debug")->GetBool("debugDetail", false); } void DrawViewDetail::unsetupObject() { // Base::Console().Message("DVD::unsetupObject()\n"); App::DocumentObject* baseObj = BaseView.getValue(); DrawView* base = dynamic_cast(baseObj); if (base) { base->requestPaint(); } } void DrawViewDetail::getParameters() {} // Python Drawing feature --------------------------------------------------------- namespace App { /// @cond DOXERR PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawViewDetailPython, TechDraw::DrawViewDetail) template<> const char* TechDraw::DrawViewDetailPython::getViewProviderName() const { return "TechDrawGui::ViewProviderViewPart"; } /// @endcond // explicit template instantiation template class TechDrawExport FeaturePythonT; }// namespace App